Interaction: alert, prompt, confirm

As we'll be using the browser as our demo environment, let's see a couple of functions to interact with the user: alert, prompt and confirm.

alert

This one we've seen already. It shows a message and waits for the user to press "OK".

For example:

js run alert("Hello");

The mini-window with the message is called a modal window. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc, until they have dealt with the window. In this case - until they press "OK".

prompt

The function prompt accepts two arguments:

js no-beautify result = prompt(title, [default]);

It shows a modal window with a text message, an input field for the visitor, and the buttons OK/Cancel.

title
The text to show the visitor.
default
An optional second parameter, the initial value for the input field.

smart header="The square brackets in syntax `[...]`" The square brackets around `default` in the syntax above denote that the parameter is optional, not required.

The visitor can type something in the prompt input field and press OK. Then we get that text in the result. Or they can cancel the input by pressing Cancel or hitting the key:Esc key, then we get null as the result.

The call to prompt returns the text from the input field or null if the input was canceled.

For instance:

run let age = prompt(‘How old are you?''', 100);

alert( You are ${age} years old!); // You are 100 years old!

warn header="In IE: always supply adefault " The second parameter is optional, but if we don't supply it, Internet Explorer will insert the text"undefined"` into the prompt.

Run this code in Internet Explorer to see:

js run let test = prompt("Test");

So, for prompts to look good in IE, we recommend always providing the second argument:

js run let test = prompt("Test", ''); // <-- for IE

confirm

The syntax:


result 
=
confirm(question)
;


The function confirm shows a modal window with a question and two buttons: OK and Cancel.

The result is true if OK is pressed and false otherwise.

For example:

run let isBoss = confirm("Are you the boss?");

alert( isBoss ); // true if OK is pressed

Summary

We covered 3 browser-specific functions to interact with visitors:

alert
shows a message.
prompt
shows a message asking the user to input text. It returns the text or, if Cancel button or key:Esc is clicked, null.
confirm
shows a message and waits for the user to press "OK" or "Cancel". It returns true for OK and false for Cancel/ key:Esc.

All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed.

There are two limitations shared by all the methods above:

  1. The exact location of the modal window is determined by the browser. Usually, it's in the center.
  2. The exact look of the window also depends on the browser. We can't modify it.

That is the price for simplicity. There are other ways to show nicer windows and richer interaction with the visitor, but if "bells and whistles" do not matter much, these methods work just fine.

Styles and classes

Before we get into JavaScript's ways of dealing with styles and classes - here's an important rule. Hopefully it's obvious enough, but we still have to mention it.

There are generally two ways to style an element:

  1. Create a class in CSS and add it: <div class="...">
  2. Write properties directly into style: <div style="...">.

JavaScript can modify both classes and style properties.

We should always prefer CSS classes to style. The latter should only be used if classes "can't handle it".

For example, style is acceptable if we calculate coordinates of an element dynamically and want to set them from JavaScript, like this:



let top 
=
/* complex calculations */
;


let left 
=
/* complex calculations */
;



elem.
style.
left
= left
;
// e.g '123px', calculated at run-time


elem.
style.
top
= top
;
// e.g '456px'


For other cases, like making the text red, adding a background icon - describe that in CSS and then add the class (JavaScript can do that). That's more flexible and easier to support.

className and classList

Changing a class is one of the most often used actions in scripts.

In the ancient time, there was a limitation in JavaScript: a reserved word like "class" could not be an object property. That limitation does not exist now, but at that time it was impossible to have a "class" property, like elem.class.

So for classes the similar-looking property "className" was introduced: the elem.className corresponds to the "class" attribute.

For instance:

html run <body class="main page"> <script> alert(document.body.className); // main page </script> </body>

If we assign something to elem.className, it replaces the whole string of classes. Sometimes that's what we need, but often we want to add/remove a single class.

There's another property for that: elem.classList.

The elem.classList is a special object with methods to add/remove/toggle a single class.

For instance:

run

So we can operate both on the full class string using className or on individual classes using classList. What we choose depends on our needs.

Methods of classList:

Besides, classList is iterable, so we can list all classes with for..of, like this:

html run <body class="main page"> <script> for (let name of document.body.classList) { alert(name); // main, and then page } </script> </body>

Element style

The property elem.style is an object that corresponds to what's written in the "style" attribute. Setting elem.style.width="100px" works the same as if we had in the attribute style a string width:100px.

For multi-word property the camelCase is used:

js no-beautify background-color => elem.style.backgroundColor z-index => elem.style.zIndex border-left-width => elem.style.borderLeftWidth

For instance:

js run document.body.style.backgroundColor = prompt('background color?', 'green');

smart header="Prefixed properties" Browser-prefixed properties like-moz-border-radius ,-webkit-border-radius` also follow the same rule: a dash means upper case.

For instance:



button.
style.
MozBorderRadius
=
'5px'
;


button.
style.
WebkitBorderRadius
=
'5px'
;


Resetting the style property

Sometimes we want to assign a style property, and later remove it.

For instance, to hide an element, we can set elem.style.display = "none".

Then later we may want to remove the style.display as if it were not set. Instead of delete elem.style.display we should assign an empty string to it: elem.style.display = "".

run // if we run this code, the

Computed styles: getComputedStyle

So, modifying a style is easy. But how to read it?

For instance, we want to know the size, margins, the color of an element. How to do it?

The style property operates only on the value of the "style" attribute, without any CSS cascade.

So we can't read anything that comes from CSS classes using elem.style.

For instance, here style doesn't see the margin:

run height=60 no-beautify The red text

…But what if we need, say, to increase the margin by 20px? We would want the current value of it.

There's another method for that: getComputedStyle.

The syntax is:



getComputedStyle(element
, [pseudo])


element
Element to read the value for.
pseudo
A pseudo-element if required, for instance ::before. An empty string or no argument means the element itself.

The result is an object with styles, like elem.style, but now with respect to all CSS classes.

For instance:

run height=100

smart header="Computed and resolved values" There are two concepts in CSS:

  1. A computed style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like height:1em or font-size:125%.
  2. A resolved style value is the one finally applied to the element. Values like 1em or 125% are relative. The browser takes the computed value and makes all units fixed and absolute, for instance: height:20px or font-size:16px. For geometry properties resolved values may have a floating point, like width:50.5px.

A long time ago getComputedStyle was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed.

So nowadays getComputedStyle actually returns the resolved value of the property, usually in

warn header="getComputedStyle requires the full property name" We should always ask for the exact property that we want, likepaddingLeft ormarginTop orborderTopWidth`. Otherwise the correct result is not guaranteed.

For instance, if there are properties paddingLeft/paddingTop, then what should we get for getComputedStyle(elem).padding? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here.

There are other inconsistencies. As an example, some browsers (Chrome) show 10px in the document below, and some of them (Firefox) - do not:

html run <style> body { margin: 10px; } </style> <script> let style = getComputedStyle(document.body); alert(style.margin); // empty string in Firefox </script>

`` smart header="Styles applied to:visited links are hidden!" Visited links may be colored using:visited` CSS pseudoclass.

But getComputedStyle does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles.

JavaScript may not see the styles applied by :visited. And also, there's a limitation in CSS that forbids applying geometry-changing styles in :visited. That's to guarantee that there's no side way for an evil page to test if a link was visited and hence to break the privacy.

Summary

To manage classes, there are two DOM properties:

To change the styles:

To read the resolved styles (with respect to all classes, after all CSS is applied and final values are calculated):

Element size and scrolling

There are many JavaScript properties that allow us to read information about element width, height and other geometry features.

We often need them when moving or positioning elements in JavaScript.

Sample element

As a sample element to demonstrate properties we'll use the one given below:

html no-beautify <div id="example"> ...Text... </div> <style> #example { width: 300px; height: 200px; border: 25px solid #E8C48F; padding: 20px; overflow: auto; } </style>

It has the border, padding and scrolling. The full set of features. There are no margins, as they are not the part of the element itself, and there are no special properties for them.

The element looks like this:

You can open the document in the sandbox.

smart header="Mind the scrollbar" The picture above demonstrates the most complex case when the element has a scrollbar. Some browsers (not all) reserve the space for it by taking it from the content (labeled as "content width" above).

So, without scrollbar the content width would be 300px, but if the scrollbar is 16px wide (the width may vary between devices and browsers) then only 300 - 16 = 284px remains, and we should take it into account. That's why examples from this chapter assume that there's a scrollbar. Without it, some calculations are simpler.

smart header="The `padding-bottom` area may be filled with text" Usually paddings are shown empty on our illustrations, but if there's a lot of text in the element and it overflows, then browsers show the "overflowing" text at `padding-bottom`, that's normal.

Geometry

Here's the overall picture with geometry properties:

Values of these properties are technically numbers, but these numbers are "of pixels", so these are pixel measurements.

Let's start exploring the properties starting from the outside of the element.

offsetParent, offsetLeft/Top

These properties are rarely needed, but still they are the "most outer" geometry properties, so we'll start with them.

The offsetParent is the nearest ancestor that the browser uses for calculating coordinates during rendering.

That's the nearest ancestor that is one of the following:

  1. CSS-positioned ( position is absolute, relative, fixed or sticky), or
  2. <td>, <th>, or <table>, or
  3. <body>.

Properties offsetLeft/offsetTop provide x/y coordinates relative to offsetParent upper-left corner.

In the example below the inner <div> has <main> as offsetParent and offsetLeft/offsetTop shifts from its upper-left corner ( 180):

html run height=10 <main style="position: relative" id="main"> <article> <div id="example" style="position: absolute; left: 180px; top: 180px">...</div> </article> </main> <script> alert(example.offsetParent.id); // main alert(example.offsetLeft); // 180 (note: a number, not a string "180px") alert(example.offsetTop); // 180 </script>

There are several occasions when offsetParent is null:

  1. For not shown elements ( display:none or not in the document).
  2. For <body> and <html>.
  3. For elements with position:fixed.

offsetWidth/Height

Now let's move on to the element itself.

These two properties are the simplest ones. They provide the outer" width/height of the element. Or, in other words, its full size including borders.

For our sample element:

smart header="Geometry properties are zero/null for elements that are not displayed" Geometry properties are calculated only for displayed elements.

If an element (or any of its ancestors) has display:none or is not in the document, then all geometry properties are zero (or null for offsetParent).

For example, offsetParent is null, and offsetWidth, offsetHeight are 0 when we created an element, but haven't inserted it into the document yet, or it (or it's ancestor) has display:none.

We can use this to check if an element is hidden, like this:



function
isHidden(elem) 
{


return
!
elem.
offsetWidth
&&
!
elem.
offsetHeight
;


}


Please note that such isHidden returns true for elements that are on-screen, but have zero sizes (like an empty <div>).

clientTop/Left

Inside the element we have the borders.

To measure them, there are properties clientTop and clientLeft.

In our example:

…But to be precise - these properties are not border width/height, but rather relative coordinates of the inner side from the outer side.

What's the difference?

It becomes visible when the document is right-to-left (the operating system is in Arabic or Hebrew languages). The scrollbar is then not on the right, but on the left, and then clientLeft also includes the scrollbar width.

In that case, clientLeft would be not 25, but with the scrollbar width 25 + 16 = 41.

Here's the example in hebrew:

clientWidth/Height

These properties provide the size of the area inside the element borders.

They include the content width together with paddings, but without the scrollbar:

On the picture above let's first consider clientHeight.

There's no horizontal scrollbar, so it's exactly the sum of what's inside the borders: CSS-height 200px plus top and bottom paddings ( 2 * 20px) total 240px.

Now clientWidth - here the content width is not 300px, but 284px, because 16px are occupied by the scrollbar. So the sum is 284px plus left and right paddings, total 324px.

If there are no paddings, then clientWidth/Height is exactly the content area, inside the borders and the scrollbar (if any).

So when there's no padding we can use clientWidth/clientHeight to get the content area size.

scrollWidth/Height

These properties are like clientWidth/clientHeight, but they also include the scrolled out (hidden) parts:

On the picture above:

We can use these properties to expand the element wide to its full width/height.

Like this:



// expand the element to the full content height


element.
style.
height
=
`
${
element.
scrollHeight
}
px`
;


Click the button to expand the element:
<div id="element" style="width:300px;height:200px; padding: 0;overflow: auto; border:1px solid black;">text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text</div>
<button style="padding:0" onclick="element.style.height = `${element.scrollHeight}px`">element.style.height = `${element.scrollHeight}px`</button>

scrollLeft/scrollTop

Properties scrollLeft/scrollTop are the width/height of the hidden, scrolled out part of the element.

On the picture below we can see scrollHeight and scrollTop for a block with a vertical scroll.

In other words, scrollTop is "how much is scrolled up".

smart header="scrollLeft/scrollTop can be modified" Most of the geometry properties here are read-only, butscrollLeft/scrollTop` can be changed, and the browser will scroll the element.

If you click the element below, the code `elem.scrollTop += 10` executes. That makes the element content scroll `10px` down.
<div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Click<br>Me<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div>

Setting scrollTop to 0 or a big value, such as 1e9 will make the element scroll to the very top/bottom respectively.

Don't take width/height from CSS

We've just covered geometry properties of DOM elements, that can be used to get widths, heights and calculate distances.

But as we know from the chapter info:styles-and-classes, we can read CSS-height and width using getComputedStyle.

So why not to read the width of an element with getComputedStyle, like this?

run let elem = document.body;

alert( getComputedStyle(elem).width ); // show CSS width for elem

Why should we use geometry properties instead? There are two reasons:

  1. First, CSS width/height depend on another property: box-sizing that defines "what is" CSS width and height. A change in box-sizing for CSS purposes may break such JavaScript.
  2. Second, CSS width/height may be auto, for instance for an inline element:

    run Hello!

    From the CSS standpoint, width:auto is perfectly normal, but in JavaScript we need an exact size in px that we can use in calculations. So here CSS width is useless.

And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar becomes buggy with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is less than CSS width. And clientWidth/clientHeight take that into account.

…But with getComputedStyle(elem).width the situation is different. Some browsers (e.g. Chrome) return the real inner width, minus the scrollbar, and some of them (e.g. Firefox) - CSS width (ignore the scrollbar). Such cross-browser differences is the reason not to use getComputedStyle, but rather rely on geometry properties.

If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below.
[iframe src="cssWidthScroll" link border=1]
The element with text has CSS `width:300px`.
On a Desktop Windows OS, Firefox, Chrome, Edge all reserve the space for the scrollbar. But  Firefox shows `300px`, while Chrome and Edge show less. That's because Firefox returns the CSS width and other browsers return the "real" width.

Please note that the described difference is only about reading getComputedStyle(...).width from JavaScript, visually everything is correct.

Summary

Elements have the following geometry properties:

All properties are read-only except scrollLeft/scrollTop that make the browser scroll the element if changed.

Window sizes and scrolling

How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript?

For this type of information, we can use the root document element document.documentElement, that corresponds to the <html> tag. But there are additional methods and peculiarities to consider.

Width/height of the window

To get window width and height, we can use the clientWidth/clientHeight of document.documentElement:

For instance, this button shows the height of your window:
<button onclick="alert(document.documentElement.clientHeight)">alert(document.documentElement.clientHeight)</button>

warn header="Notwindow.innerWidth/innerHeight " Browsers also support properties likewindow.innerWidth/innerHeight`. They look like what we want, so why not to use them instead?

If there exists a scrollbar, and it occupies some space, clientWidth/clientHeight provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content.

window.innerWidth/innerHeight includes the scrollbar.

If there's a scrollbar, and it occupies some space, then these two lines show different values: js run alert( window.innerWidth ); // full window width alert( document.documentElement.clientWidth ); // window width minus the scrollbar

In most cases, we need the available window width in order to draw or position something within scrollbars (if there are any), so we should use documentElement.clientHeight/clientWidth.

`` warn header="DOCTYPE is important" Please note: top-level geometry properties may work a little bit differently when there's no<!DOCTYPE HTML>` in HTML. Odd things are possible.

In modern HTML we should always write DOCTYPE.

Width/height of the document

Theoretically, as the root document element is document.documentElement, and it encloses all the content, we could measure the document's full size as document.documentElement.scrollWidth/scrollHeight.

But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then documentElement.scrollHeight may be even less than documentElement.clientHeight! Weird, right?

To reliably obtain the full document height, we should take the maximum of these properties:

run let scrollHeight = Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight );

alert(‘Full document height, with scrolled out part:''' + scrollHeight);

Why so? Better don't ask. These inconsistencies come from ancient times, not a "smart" logic.

Get the current scroll [#page-scroll]

DOM elements have their current scroll state in their scrollLeft/scrollTop properties.

For document scroll, document.documentElement.scrollLeft/scrollTop works in most browsers, except older WebKit-based ones, like Safari (bug 5991), where we should use document.body instead of document.documentElement.

Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, window.pageXOffset/pageYOffset:

js run alert('Current scroll from the top: ' + window.pageYOffset); alert('Current scroll from the left: ' + window.pageXOffset);

These properties are read-only.

smart header="Also available as `window` properties `scrollX` and `scrollY`" For historical reasons, both properties exist, but they are the same: - `window.pageXOffset` is an alias of `window.scrollX`. - `window.pageYOffset` is an alias of `window.scrollY`.

Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll]

To scroll the page with JavaScript, its DOM must be fully built.
For instance, if we try to scroll the page with a script in `<head>`, it won't work.

Regular elements can be scrolled by changing scrollTop/scrollLeft.

We can do the same for the page using document.documentElement.scrollTop/scrollLeft (except Safari, where document.body.scrollTop/Left should be used instead).

Alternatively, there's a simpler, universal solution: special methods window.scrollBy(x,y) and window.scrollTo(pageX,pageY).

These methods work for all browsers the same way.

scrollIntoView

For completeness, let's cover one more method: elem.scrollIntoView(top).

The call to elem.scrollIntoView(top) scrolls the page to make elem visible. It has one argument:

The button below scrolls the page to position itself at the window top:
<button onclick="this.scrollIntoView()">this.scrollIntoView()</button>
And this button scrolls the page to position itself at the bottom:
<button onclick="this.scrollIntoView(false)">this.scrollIntoView(false)</button>

Forbid the scrolling

Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document.

To make the document unscrollable, it's enough to set document.body.style.overflow = "hidden". The page will "freeze" at its current scroll position.

Try it:
<button onclick="document.body.style.overflow = 'hidden'">document.body.style.overflow = 'hidden'</button>
<button onclick="document.body.style.overflow = ''">document.body.style.overflow = ''</button>
The first button freezes the scroll, while the second one releases it.

We can use the same technique to freeze the scroll for other elements, not just for document.body.

The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it.

That looks a bit odd, but can be worked around if we compare clientWidth before and after the freeze. If it increased (the scrollbar disappeared), then add padding to document.body in place of the scrollbar to keep the content width the same.

Summary

Geometry:

Scrolling:

Coordinates

To move elements around we should be familiar with coordinates.

Most JavaScript methods deal with one of two coordinate systems:

  1. Relative to the window - similar to position:fixed, calculated from the window top/left edge.
  2. Relative to the document - similar to position:absolute in the document root, calculated from the document top/left edge.

When the page is scrolled to the very beginning, so that the top/left corner of the window is exactly the document top/left corner, these coordinates equal each other. But after the document shifts, window-relative coordinates of elements change, as elements move across the window, while document-relative coordinates remain the same.

On this picture we take a point in the document and demonstrate its coordinates before the scroll (left) and after it (right):

When the document scrolled: - pageY - document-relative coordinate stayed the same, it's counted from the document top (now scrolled out). - clientY - window-relative coordinate did change (the arrow became shorter), as the same point became closer to window top.

Element coordinates: getBoundingClientRect

The method elem.getBoundingClientRect() returns window coordinates for a minimal rectangle that encloses elem as an object of built-in DOMRect class.

Main DOMRect properties:

Additionally, there are derived properties:

For instance click this button to see its window coordinates:
<p><input id="brTest" type="button" value="Get coordinates using button.getBoundingClientRect() for this button" onclick='showRect(this)'/></p>
<script>
function showRect(elem) {
let r = elem.getBoundingClientRect();
alert(`x:${r.x}
y:${r.y}
width:${r.width}
height:${r.height}
top:${r.top}
bottom:${r.bottom}
left:${r.left}
right:${r.right}
`);
}
</script>
If you scroll the page and repeat, you'll notice that as window-relative button position changes, its window coordinates (`y/top/bottom` if you scroll vertically) change as well.

Here's the picture of elem.getBoundingClientRect() output:

As you can see, x/y and width/height fully describe the rectangle. Derived properties can be easily calculated from them:

Please note:

`` smart header="Why derived properties are needed? Why doestop/left exist if there'sx/y ?" Mathematically, a rectangle is uniquely defined with its starting point(x,y) and the direction vector(width,height)`. So the additional derived properties are for convenience.

Technically it's possible for width/height to be negative, that allows for "directed" rectangle, e.g. to represent mouse selection with properly marked start and end.

Negative width/height values mean that the rectangle starts at its bottom-right corner and then "grows" left-upwards.

Here's a rectangle with negative width and height (e.g. width=-200, height=-100):

As you can see, left/top do not equal x/y in such case.

In practice though, elem.getBoundingClientRect() always returns positive width/height, here we mention negative width/height only for you to understand why these seemingly duplicate properties are not actually duplicates.

`` warn header="Internet Explorer: no support forx/y " Internet Explorer doesn't supportx/y` properties for historical reasons.

So we can either make a polyfill (add getters in DomRect.prototype) or just use top/left, as they are always the same as x/y for positive width/height, in particular in the result of elem.getBoundingClientRect().

`` warn header="Coordinates right/bottom are different from CSS position properties" There are obvious similarities between window-relative coordinates and CSSposition:fixed`.

But in CSS positioning, right property means the distance from the right edge, and bottom property means the distance from the bottom edge.

If we just look at the picture above, we can see that in JavaScript it is not so. All window coordinates are counted from the top-left corner, including these ones.

elementFromPoint(x, y) [#elementFromPoint]

The call to document.elementFromPoint(x, y) returns the most nested element at window coordinates (x, y).

The syntax is:



let elem 
=
document.
elementFromPoint(x
, y)
;


For instance, the code below highlights and outputs the tag of the element that is now in the middle of the window:

run let centerX = document.documentElement.clientWidth / 2; let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);

elem.style.background = "red"; alert(elem.tagName);

As it uses window coordinates, the element may be different depending on the current scroll position.

warn header="For out-of-window coordinates theelementFromPoint returnsnull " The methoddocument.elementFromPoint(x,y) only works if(x,y)` are inside the visible area.

If any of the coordinates is negative or exceeds the window width/height, then it returns null.

Here's a typical error that may occur if we don't check for it:



let elem 
=
document.
elementFromPoint(x
, y)
;


// if the coordinates happen to be out of the window, then elem = null


*!*


elem.
style.
background
=
''
;
// Error!


*
/!
*


Using for "fixed" positioning

Most of time we need coordinates in order to position something.

To show something near an element, we can use getBoundingClientRect to get its coordinates, and then CSS position together with left/top (or right/bottom).

For instance, the function createMessageUnder(elem, html) below shows the message under elem:



let elem 
=
document.
getElementById(
"coords-show-mark")
;



function
createMessageUnder(elem
, html) 
{


// create message element


let message 
=
document.
createElement(
'div')
;


// better to use a css class for the style here


message.
style.
cssText
=
"position:fixed; color: red"
;



*!*


// assign coordinates, don't forget "px"!


let coords 
=
elem.
getBoundingClientRect()
;



message.
style.
left
=
coords.
left
+
"px"
;


message.
style.
top
=
coords.
bottom
+
"px"
;


*
/!
*



  message.innerHTML = html;



  return message;


}



// Usage:


// add it for 5 seconds in the document


let message = createMessageUnder
(
elem, 'Hello, world!'
)
;


document.body.append
(
message
)
;


setTimeout
(()
 => message.remove
()
, 5000
)
;


Click the button to run it:
<button id="coords-show-mark">Button with id="coords-show-mark", the message will appear under it</button>

The code can be modified to show the message at the left, right, below, apply CSS animations to "fade it in" and so on. That's easy, as we have all the coordinates and sizes of the element.

But note the important detail: when the page is scrolled, the message flows away from the button.

The reason is obvious: the message element relies on position:fixed, so it remains at the same place of the window while the page scrolls away.

To change that, we need to use document-based coordinates and position:absolute.

Document coordinates [#getCoords]

Document-relative coordinates start from the upper-left corner of the document, not the window.

In CSS, window coordinates correspond to position:fixed, while document coordinates are similar to position:absolute on top.

We can use position:absolute and top/left to put something at a certain place of the document, so that it remains there during a page scroll. But we need the right coordinates first.

There's no standard method to get the document coordinates of an element. But it's easy to write it.

The two coordinate systems are connected by the formula: - pageY = clientY + height of the scrolled-out vertical part of the document. - pageX = clientX + width of the scrolled-out horizontal part of the document.

The function getCoords(elem) will take window coordinates from elem.getBoundingClientRect() and add the current scroll to them:



// get document coordinates of the element


function
getCoords(elem) 
{


let box 
=
elem.
getBoundingClientRect()
;



return
{


top
:
box.
top
+
window.
pageYOffset
,


right
:
box.
right
+
window.
pageXOffset
,


bottom
:
box.
bottom
+
window.
pageYOffset
,


left
:
box.
left
+
window.
pageXOffset


};


}


If in the example above we used it with position:absolute, then the message would stay near the element on scroll.

The modified createMessageUnder function:



function
createMessageUnder(elem
, html) 
{


let message 
=
document.
createElement(
'div')
;


message.
style.
cssText
=
"*!*position:absolute*/!*; color: red"
;



let coords 
=
*!*
getCoords(elem)
;*
/!
*



  message.style.left = coords.left 
+
 "px";


  message.style.top = coords.bottom 
+
 "px";



  message.innerHTML = html;



  return message;


}


Summary

Any point on the page has coordinates:

  1. Relative to the window - elem.getBoundingClientRect().
  2. Relative to the document - elem.getBoundingClientRect() plus the current page scroll.

Window coordinates are great to use with position:fixed, and document coordinates do well with position:absolute.

Both coordinate systems have their pros and cons; there are times we need one or the other one, just like CSS position absolute and fixed.

Introduction to browser events

An event is a signal that something has happened. All DOM nodes generate such signals (but events are not limited to DOM).

Here's a list of the most useful DOM events, just to take a look at:

Mouse events: - click - when the mouse clicks on an element (touchscreen devices generate it on a tap). - contextmenu - when the mouse right-clicks on an element. - mouseover / mouseout - when the mouse cursor comes over / leaves an element. - mousedown / mouseup - when the mouse button is pressed / released over an element. - mousemove - when the mouse is moved.

Keyboard events: - keydown and keyup - when a keyboard key is pressed and released.

Form element events: - submit - when the visitor submits a <form>. - focus - when the visitor focuses on an element, e.g. on an <input>.

Document events: - DOMContentLoaded - when the HTML is loaded and processed, DOM is fully built.

CSS events: - transitionend - when a CSS-animation finishes.

There are many other events. We'll get into more details of particular events in next chapters.

Event handlers

To react on events we can assign a handler - a function that runs in case of an event.

Handlers are a way to run JavaScript code in case of user actions.

There are several ways to assign a handler. Let's see them, starting from the simplest one.

HTML-attribute

A handler can be set in HTML with an attribute named on<event>.

For instance, to assign a click handler for an input, we can use onclick, like here:

html run <input value="Click me" *!*onclick="alert('Click!')"*/!* type="button">

On mouse click, the code inside onclick runs.

Please note that inside onclick we use single quotes, because the attribute itself is in double quotes. If we forget that the code is inside the attribute and use double quotes inside, like this: onclick="alert("Click!")", then it won't work right.

An HTML-attribute is not a convenient place to write a lot of code, so we'd better create a JavaScript function and call it there.

Here a click runs the function countRabbits():

autorun height=50

<input type="button" !onclick="countRabbits()" /! value="Count rabbits!">

As we know, HTML attribute names are not case-sensitive, so ONCLICK works as well as onClick and onCLICK… But usually attributes are lowercased: onclick.

DOM property

We can assign a handler using a DOM property on<event>.

For instance, elem.onclick:

html autorun <input id="elem" type="button" value="Click me"> <script> *!* elem.onclick = function() { alert('Thank you'); }; */!* </script>

If the handler is assigned using an HTML-attribute then the browser reads it, creates a new function from the attribute content and writes it to the DOM property.

So this way is actually the same as the previous one.

These two code pieces work the same:

  1. Only HTML:

    html autorun height=50 <input type="button" *!*onclick="alert('Click!')"*/!* value="Button">
  2. HTML + JS:

    html autorun height=50 <input type="button" id="button" value="Button"> <script> *!* button.onclick = function() { alert('Click!'); }; */!* </script>

In the first example, the HTML attribute is used to initialize the button.onclick, while in the second example - the script, that's all the difference.

As there's only one onclick property, we can't assign more than one event handler.

In the example below adding a handler with JavaScript overwrites the existing handler:

html run height=50 autorun <input type="button" id="elem" onclick="alert('Before')" value="Click me"> <script> *!* elem.onclick = function() { // overwrites the existing handler alert('After'); // only this will be shown }; */!* </script>

To remove a handler - assign elem.onclick = null.

Accessing the element: this

The value of this inside a handler is the element. The one which has the handler on it.

In the code below button shows its contents using this.innerHTML:

html height=50 autorun <button onclick="alert(this.innerHTML)">Click me</button>

Possible mistakes

If you're starting to work with events - please note some subtleties.

We can set an existing function as a handler:



function
sayThanks() 
{


alert(
'Thanks!')
;


}



elem.
onclick
= sayThanks
;


But be careful: the function should be assigned as sayThanks, not sayThanks().



// right


button.
onclick
= sayThanks
;



// wrong


button.
onclick
=
sayThanks()
;


If we add parentheses, then sayThanks() becomes is a function call. So the last line actually takes the result of the function execution, that is undefined (as the function returns nothing), and assigns it to onclick. That doesn't work.

…On the other hand, in the markup we do need the parentheses:



<input
 type=
"button"
 id=
"button"
 onclick=
"sayThanks()"
>


The difference is easy to explain. When the browser reads the attribute, it creates a handler function with body from the attribute content.

So the markup generates this property:



button.
onclick
=
function() 
{


*!*


sayThanks()
;
// <-- the attribute content goes here


*
/!
*


};


Don't use setAttribute for handlers.

Such a call won't work:

js run no-beautify // a click on <body> will generate errors, // because attributes are always strings, function becomes a string document.body.setAttribute('onclick', function() { alert(1) });

DOM-property case matters.

Assign a handler to elem.onclick, not elem.ONCLICK, because DOM properties are case-sensitive.

addEventListener

The fundamental problem of the aforementioned ways to assign handlers - we can't assign multiple handlers to one event.

Let's say, one part of our code wants to highlight a button on click, and another one wants to show a message on the same click.

We'd like to assign two event handlers for that. But a new DOM property will overwrite the existing one:

js no-beautify input.onclick = function() { alert(1); } // ... input.onclick = function() { alert(2); } // replaces the previous handler

Developers of web standards understood that long ago and suggested an alternative way of managing handlers using special methods addEventListener and removeEventListener. They are free of such a problem.

The syntax to add a handler:



element.
addEventListener(event
, handler
, [options])
;


event
Event name, e.g. "click".
handler
The handler function.
options
An additional optional object with properties: - once: if true, then the listener is automatically removed after it triggers. - capture: the phase where to handle the event, to be covered later in the chapter info:bubbling-and-capturing. For historical reasons, options can also be false/true, that's the same as {capture: false/true}. - passive: if true, then the handler will not call preventDefault(), we'll explain that later in info:default-browser-action.

To remove the handler, use removeEventListener:



element.
removeEventListener(event
, handler
, [options])
;


warn header="Removal requires the same function" To remove a handler we should pass exactly the same function as was assigned.

This doesn't work:

js no-beautify elem.addEventListener( "click" , () => alert('Thanks!')); // .... elem.removeEventListener( "click", () => alert('Thanks!'));

The handler won't be removed, because removeEventListener gets another function - with the same code, but that doesn't matter, as it's a different function object.

Here's the right way:



function
handler() 
{


alert( 
'Thanks!' )
;


}



input.
addEventListener(
"click"
, handler)
;


// ....


input.
removeEventListener(
"click"
, handler)
;


Please note - if we don't store the function in a variable, then we can't remove it. There's no way to "read back" handlers assigned by addEventListener.

Multiple calls to addEventListener allow to add multiple handlers, like this:

run no-beautify

As we can see in the example above, we can set handlers both using a DOM-property and addEventListener. But generally we use only one of these ways.

warn header="For some events, handlers only work withaddEventListener " There exist events that can't be assigned via a DOM-property. Only withaddEventListener`.

For instance, the DOMContentLoaded event, that triggers when the document is loaded and DOM is built.



// will never run


document.
onDOMContentLoaded
=
function() 
{


alert(
"DOM built")
;


};




// this way it works


document.
addEventListener(
"DOMContentLoaded"
,
function() 
{


alert(
"DOM built")
;


})
;


So addEventListener is more universal. Although, such events are an exception rather than the rule.

Event object

To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keydown", but what were the pointer coordinates? Which key was pressed? And so on.

When an event happens, the browser creates an event object, puts details into it and passes it as an argument to the handler.

Here's an example of getting pointer coordinates from the event object:

run

Some properties of event object:

event.type
Event type, here it's "click".
event.currentTarget
Element that handled the event. That's exactly the same as this, unless the handler is an arrow function, or its this is bound to something else, then we can get the element from event.currentTarget.
event.clientX / event.clientY
Window-relative coordinates of the cursor, for pointer events.

There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when we come to different events in details.

smart header="The event object is also available in HTML handlers" If we assign a handler in HTML, we can also use theevent` object, like this:

html autorun height=60 <input type="button" onclick="*!*alert(event.type)*/!*" value="Event type">

That's possible because when the browser reads the attribute, it creates a handler like this: function(event) { alert(event.type) }. That is: its first argument is called "event", and the body is taken from the attribute.

Object handlers: handleEvent

We can assign not just a function, but an object as an event handler using addEventListener. When an event occurs, its handleEvent method is called.

For instance:

run

As we can see, when addEventListener receives an object as the handler, it calls obj.handleEvent(event) in case of an event.

We could also use a class for that:

run

Here the same object handles both events. Please note that we need to explicitly setup the events to listen using addEventListener. The menu object only gets mousedown and mouseup here, not any other types of events.

The method handleEvent does not have to do all the job by itself. It can call other event-specific methods instead, like this:

run

Now event handlers are clearly separated, that may be easier to support.

Summary

There are 3 ways to assign event handlers:

  1. HTML attribute: onclick="...".
  2. DOM property: elem.onclick = function.
  3. Methods: elem.addEventListener(event, handler[, phase]) to add, removeEventListener to remove.

HTML attributes are used sparingly, because JavaScript in the middle of an HTML tag looks a little bit odd and alien. Also can't write lots of code in there.

DOM properties are ok to use, but we can't assign more than one handler of the particular event. In many cases that limitation is not pressing.

The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance transitionend and DOMContentLoaded (to be covered). Also addEventListener supports objects as event handlers. In that case the method handleEvent is called in case of the event.

No matter how you assign the handler - it gets an event object as the first argument. That object contains the details about what's happened.

We'll learn more about events in general and about different types of events in the next chapters.

Bubbling and capturing

Let's start with an example.

This handler is assigned to <div>, but also runs if you click any nested tag like <em> or <code>:

html autorun height=60 <div onclick="alert('The handler!')"> <em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em> </div>

Isn't it a bit strange? Why does the handler on <div> run if the actual click was on <em>?

Bubbling

The bubbling principle is simple.

When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.

Let's say we have 3 nested elements FORM > DIV > P with a handler on each of them:

run autorun
FORM
DIV

P

A click on the inner <p> first runs onclick: 1. On that <p>. 2. Then on the outer <div>. 3. Then on the outer <form>. 4. And so on upwards till the document object.

So if we click on <p>, then we'll see 3 alerts: p -> div -> form.

The process is called "bubbling", because events "bubble" from the inner element up through parents like a bubble in the water.

warn header=" Almost all events bubble." The key word in this phrase is "almost".

For instance, a focus event does not bubble. There are other examples too, we'll meet them. But still it's an exception, rather than a rule, most events do bubble.

event.target

A handler on a parent element can always get the details about where it actually happened.

The most deeply nested element that caused the event is called a target element, accessible as event.target.

Note the differences from this (= event.currentTarget):

For instance, if we have a single handler form.onclick, then it can "catch" all clicks inside the form. No matter where the click happened, it bubbles up to <form> and runs the handler.

In form.onclick handler:

Check it out:

[codetabs height=220 src="bubble-target"]

It's possible that event.target could equal this - it happens when the click is made directly on the <form> element.

Stopping bubbling

A bubbling event goes from the target element straight up. Normally it goes upwards till <html>, and then to document object, and some events even reach window, calling all handlers on the path.

But any handler may decide that the event has been fully processed and stop the bubbling.

The method for it is event.stopPropagation().

For instance, here body.onclick doesn't work if you click on <button>:

html run autorun height=60 <body onclick="alert(`the bubbling doesn't reach here`)"> <button onclick="event.stopPropagation()">Click me</button> </body>

smart header="event.stopImmediatePropagation()" If an element has multiple event handlers on a single event, then even if one of them stops the bubbling, the other ones still execute.

In other words, event.stopPropagation() stops the move upwards, but on the current element all other handlers will run.

To stop the bubbling and prevent handlers on the current element from running, there's a method event.stopImmediatePropagation(). After it no other handlers execute.

warn header="Don't stop bubbling without a need!" Bubbling is convenient. Don't stop it without a real need: obvious and architecturally well thought out.

Sometimes event.stopPropagation() creates hidden pitfalls that later may become problems.

For instance:

  1. We create a nested menu. Each submenu handles clicks on its elements and calls stopPropagation so that the outer menu won't trigger.
  2. Later we decide to catch clicks on the whole window, to track users' behavior (where people click). Some analytic systems do that. Usually the code uses document.addEventListener('click'…) to catch all clicks.
  3. Our analytic won't work over the area where clicks are stopped by stopPropagation. Sadly, we've got a "dead zone".

There's usually no real need to prevent the bubbling. A task that seemingly requires that may be solved by other means. One of them is to use custom events, we'll cover them later. Also we can write our data into the event object in one handler and read it in another one, so we can pass to handlers on parents information about the processing below.

Capturing

There's another phase of event processing called "capturing". It is rarely used in real code, but sometimes can be useful.

The standard DOM Events describes 3 phases of event propagation:

  1. Capturing phase - the event goes down to the element.
  2. Target phase - the event reached the target element.
  3. Bubbling phase - the event bubbles up from the element.

Here's the picture of a click on <td> inside a table, taken from the specification:

That is: for a click on <td> the event first goes through the ancestors chain down to the element (capturing phase), then it reaches the target and triggers there (target phase), and then it goes up (bubbling phase), calling handlers on its way.

Before we only talked about bubbling, because the capturing phase is rarely used. Normally it is invisible to us.

Handlers added using on<event>-property or using HTML attributes or using two-argument addEventListener(event, handler) don't know anything about capturing, they only run on the 2nd and 3rd phases.

To catch an event on the capturing phase, we need to set the handler capture option to true:



  elem.
  addEventListener(...
  ,
  {
  capture
  :
  true
  })


  // or, just "true" is an alias to {capture: true}


  elem.
  addEventListener(...
  ,
  true)


There are two possible values of the capture option:

Note that while formally there are 3 phases, the 2nd phase ("target phase": the event reached the element) is not handled separately: handlers on both capturing and bubbling phases trigger at that phase.

Let's see both capturing and bubbling in action:

run autorun height=140 edit
FORM
DIV

P

The code sets click handlers on every element in the document to see which ones are working.

If you click on <p>, then the sequence is:

  1. HTML -> BODY -> FORM -> DIV (capturing phase, the first listener):
  2. P (target phase, triggers two times, as we've set two listeners: capturing and bubbling)
  3. DIV -> FORM -> BODY -> HTML (bubbling phase, the second listener).

There's a property event.eventPhase that tells us the number of the phase on which the event was caught. But it's rarely used, because we usually know it in the handler.

smart header="To remove the handler, `removeEventListener` needs the same phase" If we `addEventListener(..., true)`, then we should mention the same phase in `removeEventListener(..., true)` to correctly remove the handler.

smart header="Listeners on same element and same phase run in their set order" If we have multiple event handlers on the same phase, assigned to the same element withaddEventListener`, they run in the same order as they are created:



  elem.
  addEventListener(
  "click"
  , e 
  =>
  alert(
  1))
  ;
  // guaranteed to trigger first


  elem.
  addEventListener(
  "click"
  , e 
  =>
  alert(
  2))
  ;


Summary

When an event happens - the most nested element where it happens gets labeled as the "target element" ( event.target).

Each handler can access event object properties:

Any event handler can stop the event by calling event.stopPropagation(), but that's not recommended, because we can't really be sure we won't need it above, maybe for completely different things.

The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logic behind that.

In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed.

The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular <td> may be suited for that exactly <td>, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last one.

Bubbling and capturing lay the foundation for "event delegation" - an extremely powerful event handling pattern that we study in the next chapter.

Event delegation

Capturing and bubbling allow us to implement one of most powerful event handling patterns called event delegation.

The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them - we put a single handler on their common ancestor.

In the handler we get event.target to see where the event actually happened and handle it.

Let's see an example - the Ba-Gua diagram reflecting the ancient Chinese philosophy.

Here it is:

[iframe height=350 src="bagua" edit link]

The HTML is like this:



  <table>


  <tr>


  <th
   colspan=
  "3"
  ><em>Bagua
  </em> Chart: Direction, Element, Color, Meaning
  </th>


  </tr>


  <tr>


  <td
   class=
  "nw"
  ><strong>Northwest
  </strong><br>Metal
  <br>Silver
  <br>Elders
  </td>


  <td
   class=
  "n"
  >...
  </td>


  <td
   class=
  "ne"
  >...
  </td>


  </tr>


  <tr>...2 more lines of this kind...
  </tr>


  <tr>...2 more lines of this kind...
  </tr>


  </table>


The table has 9 cells, but there could be 99 or 9999, doesn't matter.

Our task is to highlight a cell <td> on click.

Instead of assign an onclick handler to each <td> (can be many) - we'll setup the "catch-all" handler on <table> element.

It will use event.target to get the clicked element and highlight it.

The code:



  let selectedTd
  ;



  *!*


  table.
  onclick
  =
  function(event) 
  {


  let target 
  =
  event.
  target
  ;
  // where was the click?



  if (
  target.
  tagName
  !=
  'TD') 
  return
  ;
  // not on TD? Then we're not interested



  highlight(target)
  ;
  // highlight it


  };


  *
  /!
  *



  function highlight
  (
  td
  )
   {


    if 
  (
  selectedTd
  )
   { // remove the existing highlight if any


      selectedTd.classList.remove
  (
  'highlight'
  )
  ;


    }


    selectedTd = td;


    selectedTd.classList.add
  (
  'highlight'
  )
  ; // highlight the new td


  }


Such a code doesn't care how many cells there are in the table. We can add/remove <td> dynamically at any time and the highlighting will still work.

Still, there's a drawback.

The click may occur not on the <td>, but inside it.

In our case if we take a look inside the HTML, we can see nested tags inside <td>, like <strong>:



  <td>

*!*

  <strong>Northwest
  </strong>

*/!*
  ...

  </td>


Naturally, if a click happens on that <strong> then it becomes the value of event.target.

In the handler table.onclick we should take such event.target and find out whether the click was inside <td> or not.

Here's the improved code:



  table.
  onclick
  =
  function(event) 
  {


  let td 
  =
  event.
  target.
  closest(
  'td')
  ;
  // (1)



  if (
  !td) 
  return
  ;
  // (2)



  if (
  !
  table.
  contains(td)) 
  return
  ;
  // (3)



  highlight(td)
  ;
  // (4)


  };


Explanations: 1. The method elem.closest(selector) returns the nearest ancestor that matches the selector. In our case we look for <td> on the way up from the source element. 2. If event.target is not inside any <td>, then the call returns immediately, as there's nothing to do. 3. In case of nested tables, event.target may be a <td>, but lying outside of the current table. So we check if that's actually our table's <td>. 4. And, if it's so, then highlight it.

As the result, we have a fast, efficient highlighting code, that doesn't care about the total number of <td> in the table.

Delegation example: actions in markup

There are other uses for event delegation.

Let's say, we want to make a menu with buttons "Save", "Load", "Search" and so on. And there's an object with methods save, load, search… How to match them?

The first idea may be to assign a separate handler to each button. But there's a more elegant solution. We can add a handler for the whole menu and data-action attributes for buttons that has the method to call:



  <button
   *
  !*data-action
  =
  "save"
  */!*
  >Click to Save
  </button>


The handler reads the attribute and executes the method. Take a look at the working example:

autorun height=60 run untrusted

Please note that this.onClick is bound to this in (*). That's important, because otherwise this inside it would reference the DOM element ( elem), not the Menu object, and this[action] would not be what we need.

So, what advantages does delegation give us here?

+ We don't need to write the code to assign a handler to each button. Just make a method and put it in the markup.
+ The HTML structure is flexible, we can add/remove buttons at any time.

We could also use classes .action-save, .action-load, but an attribute data-action is better semantically. And we can use it in CSS rules too.

The "behavior" pattern

We can also use event delegation to add "behaviors" to elements declaratively, with special attributes and classes.

The pattern has two parts: 1. We add a custom attribute to an element that describes its behavior. 2. A document-wide handler tracks events, and if an event happens on an attributed element - performs the action.

Behavior: Counter

For instance, here the attribute data-counter adds a behavior: "increase value on click" to buttons:

run autorun height=60 Counter: One more counter:

If we click a button - its value is increased. Not buttons, but the general approach is important here.

There can be as many attributes with data-counter as we want. We can add new ones to HTML at any moment. Using the event delegation we "extended" HTML, added an attribute that describes a new behavior.

`` warn header="For document-level handlers -- alwaysaddEventListener " When we assign an event handler to thedocument object, we should always useaddEventListener , notdocument.on `, because the latter will cause conflicts: new handlers overwrite old ones.

For real projects it's normal that there are many handlers on document set by different parts of the code.

Behavior: Toggler

One more example of behavior. A click on an element with the attribute data-toggle-id will show/hide the element with the given id:

autorun run height=60 <button !data-toggle-id="subscribe-mail" /!> Show the subscription form

Let's note once again what we did. Now, to add toggling functionality to an element - there's no need to know JavaScript, just use the attribute data-toggle-id.

That may become really convenient - no need to write JavaScript for every such element. Just use the behavior. The document-level handler makes it work for any element of the page.

We can combine multiple behaviors on a single element as well.

The "behavior" pattern can be an alternative to mini-fragments of JavaScript.

Summary

Event delegation is really cool! It's one of the most helpful patterns for DOM events.

It's often used to add the same handling for many similar elements, but not only for that.

The algorithm:

  1. Put a single handler on the container.
  2. In the handler - check the source element event.target.
  3. If the event happened inside an element that interests us, then handle the event.

Benefits:

  + Simplifies initialization and saves memory: no need to add many handlers.
+ Less code: when adding or removing elements, no need to add/remove handlers.
+ DOM modifications: we can mass add/remove elements with `innerHTML` and the like.

The delegation has its limitations of course:

  - First, the event must be bubbling. Some events do not bubble. Also, low-level handlers should not use `event.stopPropagation()`.
- Second, the delegation may add CPU load, because the container-level handler reacts on events in any place of the container, no matter whether they interest us or not. But usually the load is negligible, so we don't take it into account.

Browser default actions

Many events automatically lead to certain actions performed by the browser.

For instance:

If we handle an event in JavaScript, we may not want the corresponding browser action to happen, and want to implement another behavior instead.

Preventing browser actions

There are two ways to tell the browser we don't want it to act:

In this HTML a click on a link doesn't lead to navigation, browser doesn't do anything:

html autorun height=60 no-beautify <a href="/" onclick="return false">Click here</a> or <a href="/" onclick="event.preventDefault()">here</a>

In the next example we'll use this technique to create a JavaScript-powered menu.

`` warn header="Returningfalse` from a handler is an exception" The value returned by an event handler is usually ignored.

The only exception is return false from a handler assigned using on<event>.

In all other cases, return value is ignored. In particular, there's no sense in returning true.

Example: the menu

Consider a site menu, like this:

    
      
        <ul
         id=
        "menu"
         class=
        "menu"
        >
      
      
        <li><a
         href=
        "/html"
        >HTML
        </a></li>
      
      
        <li><a
         href=
        "/javascript"
        >JavaScript
        </a></li>
      
      
        <li><a
         href=
        "/css"
        >CSS
        </a></li>
      
      
        </ul>
      
    
  

Here's how it looks with some CSS:

[iframe height=70 src="menu" link edit]

Menu items are implemented as HTML-links <a>, not buttons <button>. There are several reasons to do so, for instance:

So we use <a> in the markup. But normally we intend to handle clicks in JavaScript. So we should prevent the default browser action.

Like here:

    
      
        menu.
        onclick
        =
        function(event) 
        {
      
      
        if (
        event.
        target.
        nodeName
        !=
        'A') 
        return
        ;
      
      
      
        let href 
        =
        event.
        target.
        getAttribute(
        'href')
        ;
      
      
        alert( href )
        ;
        // ...can be loading from the server, UI generation etc
      
      
      
        *!*
      
      
        return
        false
        ;
        // prevent browser action (don't go to the URL)
      
      
        *
        /!
        *
      
      
        };
      
    
  

If we omit return false, then after our code executes the browser will do its "default action" - navigating to the URL in href. And we don't need that here, as we're handling the click by ourselves.

By the way, using event delegation here makes our menu very flexible. We can add nested lists and style them using CSS to "slide down".

smart header="Follow-up events" Certain events flow one into another. If we prevent the first event, there will be no second.

For instance, mousedown on an <input> field leads to focusing in it, and the focus event. If we prevent the mousedown event, there's no focus.

Try to click on the first <input> below - the focus event happens. But if you click the second one, there's no focus.

html run autorun <input value="Focus works" onfocus="this.value=''"> <input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Click me">

That's because the browser action is canceled on mousedown. The focusing is still possible if we use another way to enter the input. For instance, the key:Tab key to switch from the 1st input into the 2nd. But not with the mouse click any more.

The "passive" handler option

The optional passive: true option of addEventListener signals the browser that the handler is not going to call preventDefault().

Why that may be needed?

There are some events like touchmove on mobile devices (when the user moves their finger across the screen), that cause scrolling by default, but that scrolling can be prevented using preventDefault() in the handler.

So when the browser detects such event, it has first to process all handlers, and then if preventDefault is not called anywhere, it can proceed with scrolling. That may cause unnecessary delays and "jitters" in the UI.

The passive: true options tells the browser that the handler is not going to cancel scrolling. Then browser scrolls immediately providing a maximally fluent experience, and the event is handled by the way.

For some browsers (Firefox, Chrome), passive is true by default for touchstart and touchmove events.

event.defaultPrevented

The property event.defaultPrevented is true if the default action was prevented, and false otherwise.

There's an interesting use case for it.

You remember in the chapter info:bubbling-and-capturing we talked about event.stopPropagation() and why stopping bubbling is bad?

Sometimes we can use event.defaultPrevented instead, to signal other event handlers that the event was handled.

Let's see a practical example.

By default the browser on contextmenu event (right mouse click) shows a context menu with standard options. We can prevent it and show our own, like this:

autorun height=50 no-beautify run

<button !oncontextmenu="alert(‘Draw our menu'); return false" /!> Right-click shows our context menu

Now, in addition to that context menu we'd like to implement document-wide context menu.

Upon right click, the closest context menu should show up.

autorun height=80 no-beautify run

Right-click here for the document context menu

The problem is that when we click on elem, we get two menus: the button-level and (the event bubbles up) the document-level menu.

How to fix it? One of solutions is to think like: "When we handle right-click in the button handler, let's stop its bubbling" and use event.stopPropagation():

autorun height=80 no-beautify run

Right-click for the document menu

Now the button-level menu works as intended. But the price is high. We forever deny access to information about right-clicks for any outer code, including counters that gather statistics and so on. That's quite unwise.

An alternative solution would be to check in the document handler if the default action was prevented? If it is so, then the event was handled, and we don't need to react on it.

autorun height=80 no-beautify run

Right-click for the document menu (added a check for event.defaultPrevented)

Now everything also works correctly. If we have nested elements, and each of them has a context menu of its own, that would also work. Just make sure to check for event.defaultPrevented in each contextmenu handler.

smart header="event.stopPropagation() and event.preventDefault()" As we can clearly see, `event.stopPropagation()` and `event.preventDefault()` (also known as `return false`) are two different things. They are not related to each other.

`` smart header="Nested context menus architecture" There are also alternative ways to implement nested context menus. One of them is to have a single global object with a handler fordocument.oncontextmenu`, and also methods that allow us to store other handlers in it.

The object will catch any right-click, look through stored handlers and run the appropriate one.

But then each piece of code that wants a context menu should know about that object and use its help instead of the own contextmenu handler.

Summary

There are many default browser actions:

All the default actions can be prevented if we want to handle the event exclusively by JavaScript.

To prevent a default action - use either event.preventDefault() or return false. The second method works only for handlers assigned with on<event>.

The passive: true option of addEventListener tells the browser that the action is not going to be prevented. That's useful for some mobile events, like touchstart and touchmove, to tell the browser that it should not wait for all handlers to finish before scrolling.

If the default action was prevented, the value of event.defaultPrevented becomes true, otherwise it's false.

`` warn header="Stay semantic, don't abuse" Technically, by preventing default actions and adding JavaScript we can customize the behavior of any elements. For instance, we can make a link work like a button, and a button

smart header="event.isTrusted" There is a way to tell a "real" user event from a script-generated one.

The property event.isTrusted is true for events that come from real user actions and false for script-generated events.

Bubbling example

We can create a bubbling event with the name "hello" and catch it on document.

All we need is to set bubbles to true:

run no-beautify

Hello from the script!

Notes:

  1. We should use addEventListener for our custom events, because on<event> only exists for built-in events, document.onhello doesn't work.
  2. Must set bubbles:true, otherwise the event won't bubble up.

The bubbling mechanics is the same for built-in ( click) and custom ( hello) events. There are also capturing and bubbling stages.

MouseEvent, KeyboardEvent and others

Here's a short list of classes for UI Events from the UI Event specification:

We should use them instead of new Event if we want to create such events. For instance, new MouseEvent("click").

The right constructor allows to specify standard properties for that type of event.

Like clientX/clientY for a mouse event:

run let event = new MouseEvent("click", { bubbles: true, cancelable: true, clientX: 100, clientY: 100 });

! alert(event.clientX); // 100 /!

Please note: the generic Event constructor does not allow that.

Let's try:

run let event = new Event("click", { bubbles: true, // only bubbles and cancelable cancelable: true, // work in the Event constructor clientX: 100, clientY: 100 });

! alert(event.clientX); // undefined, the unknown property is ignored! /!

Technically, we can work around that by assigning directly event.clientX=100 after creation. So that's a matter of convenience and following the rules. Browser-generated events always have the right type.

The full list of properties for different UI events is in the specification, for instance, MouseEvent.

Custom events

For our own, completely new events types like "hello" we should use new CustomEvent. Technically CustomEvent is the same as Event, with one exception.

In the second argument (object) we can add an additional property detail for any custom information that we want to pass with the event.

For instance:

run refresh

Hello for John!

The detail property can have any data. Technically we could live without, because we can assign any properties into a regular new Event object after its creation. But CustomEvent provides the special detail field for it to evade conflicts with other event properties.

Besides, the event class describes "what kind of event" it is, and if the event is custom, then we should use CustomEvent just to be clear about what it is.

event.preventDefault()

Many browser events have a "default action", such as navigating to a link, starting a selection, and so on.

For new, custom events, there are definitely no default browser actions, but a code that dispatches such event may have its own plans what to do after triggering the event.

By calling event.preventDefault(), an event handler may send a signal that those actions should be canceled.

In that case the call to elem.dispatchEvent(event) returns false. And the code that dispatched it knows that it shouldn't continue.

Let's see a practical example - a hiding rabbit (could be a closing menu or something else).

Below you can see a #rabbit and hide() function that dispatches "hide" event on it, to let all interested parties know that the rabbit is going to hide.

Any handler can listen for that event with rabbit.addEventListener('hide',...) and, if needed, cancel the action using event.preventDefault(). Then the rabbit won't disappear:

run refresh autorun
|\   /|
\|_|/
/. .\
=\_Y_/=
{>o<}
  

Please note: the event must have the flag cancelable: true, otherwise the call event.preventDefault() is ignored.

Events-in-events are synchronous

Usually events are processed in a queue. That is: if the browser is processing onclick and a new event occurs, e.g. mouse moved, then it's handling is queued up, corresponding mousemove handlers will be called after onclick processing is finished.

The notable exception is when one event is initiated from within another one, e.g. using dispatchEvent. Such events are processed immediately: the new event handlers are called, and then the current event handling is resumed.

For instance, in the code below the menu-open event is triggered during the onclick.

It's processed immediately, without waiting for onclick handler to end:

run autorun

The output order is: 1 -> nested -> 2.

Please note that the nested event menu-open is caught on the document. The propagation and handling of the nested event is finished before the processing gets back to the outer code ( onclick).

That's not only about dispatchEvent, there are other cases. If an event handler calls methods that trigger other events - they are processed synchronously too, in a nested fashion.

Let's say we don't like it. We'd want onclick to be fully processed first, independently from menu-open or any other nested events.

Then we can either put the dispatchEvent (or another event-triggering call) at the end of onclick or, maybe better, wrap it in the zero-delay setTimeout:

run

Now dispatchEvent runs asynchronously after the current code execution is finished, including menu.onclick, so event handlers are totally separate.

The output order becomes: 1 -> 2 -> nested.

Summary

To generate an event from code, we first need to create an event object.

The generic Event(name, options) constructor accepts an arbitrary event name and the options object with two properties: - bubbles: true if the event should bubble. - cancelable: true if the event.preventDefault() should work.

Other constructors of native events like MouseEvent, KeyboardEvent and so on accept properties specific to that event type. For instance, clientX for mouse events.

For custom events we should use CustomEvent constructor. It has an additional option named detail, we should assign the event-specific data to it. Then all handlers can access it as event.detail.

Despite the technical possibility of generating browser events like click or keydown, we should use them with great care.

We shouldn't generate browser events as it's a hacky way to run handlers. That's bad architecture most of the time.

Native events might be generated:

Custom events with our own names are often generated for architectural purposes, to signal what happens inside our menus, sliders, carousels etc.

Mouse events

In this chapter we'll get into more details about mouse events and their properties.

Please note: such events may come not only from "mouse devices", but are also from other devices, such as phones and tablets, where they are emulated for compatibility.

Mouse event types

We've already seen some of these events:

mousedown/mouseup
Mouse button is clicked/released over an element.
mouseover/mouseout
Mouse pointer comes over/out from an element.
mousemove
Every mouse move over an element triggers that event.
click
Triggers after mousedown and then mouseup over the same element if the left mouse button was used.
dblclick
Triggers after two clicks on the same element within a short timeframe. Rarely used nowadays.
contextmenu
Triggers when the right mouse button is pressed. There are other ways to open a context menu, e.g. using a special keyboard key, it triggers in that case also, so it's not exactly the mouse event.

…There are several other events too, we'll cover them later.

Events order

As you can see from the list above, a user action may trigger multiple events.

For instance, a left-button click first triggers mousedown, when the button is pressed, then mouseup and click when it's released.

In cases when a single action initiates multiple events, their order is fixed. That is, the handlers are called in the order mousedown -> mouseup -> click.

    Click the button below and you'll see the events. Try double-click too.
On the teststand below all mouse events are logged, and if there is more than a 1 second delay between them they are separated by a horizontal ruler.
Also we can see the `button` property that allows to detect the mouse button, it's explained below.
<input onmousedown="return logMouse(event)" onmouseup="return logMouse(event)" onclick="return logMouse(event)" oncontextmenu="return logMouse(event)" ondblclick="return logMouse(event)" value="Click me with the right or the left mouse button" type="button"> <input onclick="logClear('test')" value="Clear" type="button"> <form id="testform" name="testform"> <textarea style="font-size:12px;height:150px;width:360px;"></textarea></form>
  

Mouse button

Click-related events always have the button property, which allows to get the exact mouse button.

We usually don't use it for click and contextmenu events, because the former happens only on left-click, and the latter - only on right-click.

From the other hand, mousedown and mouseup handlers may need event.button, because these events trigger on any button, so button allows to distinguish between "right-mousedown" and "left-mousedown".

The possible values of event.button are:

Button state event.button
Left button (primary) 0
Middle button (auxiliary) 1
Right button (secondary) 2
X1 button (back) 3
X2 button (forward) 4

Most mouse devices only have the left and right buttons, so possible values are 0 or 2. Touch devices also generate similar events when one taps on them.

Also there's event.buttons property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at MDN if you ever need it.

`` warn header="The outdatedevent.which " Old code may useevent.which` property that's an old non-standard way of getting a button, with possible values:

As of now, event.which is deprecated, we shouldn't use it.

Modifiers: shift, alt, ctrl and meta

All mouse events include the information about pressed modifier keys.

Event properties:

They are true if the corresponding key was pressed during the event.

For instance, the button below only works on key:Alt+Shift+click:

autorun height=60

`` warn header="Attention: on Mac it's usuallyCmd instead ofCtrl " On Windows and Linux there are modifier keyskey:Alt ,key:Shift andkey:Ctrl . On Mac there's one more:key:Cmd , corresponding to the propertymetaKey`.

In most applications, when Windows/Linux uses key:Ctrl, on Mac key:Cmd is used.

That is: where a Windows user presses key:Ctrl+Enter or key:Ctrl+A, a Mac user would press key:Cmd+Enter or key:Cmd+A, and so on.

So if we want to support combinations like key:Ctrl+click, then for Mac it makes sense to use key:Cmd+click. That's more comfortable for Mac users.

Even if we'd like to force Mac users to key:Ctrl+click - that's kind of difficult. The problem is: a left-click with key:Ctrl is interpreted as a right-click on MacOS, and it generates the contextmenu event, not click like Windows/Linux.

So if we want users of all operating systems to feel comfortable, then together with ctrlKey we should check metaKey.

For JS-code it means that we should check if (event.ctrlKey || event.metaKey).

warn header="There are also mobile devices" Keyboard combinations are good as an addition to the workflow. So that if the visitor uses a keyboard - they work.

But if their device doesn't have it - then there should be a way to live without modifier keys.

Coordinates: clientX/Y, pageX/Y

All mouse events provide coordinates in two flavours:

  1. Window-relative: clientX and clientY.
  2. Document-relative: pageX and pageY.

We already covered the difference between them in the chapter info:coordinates.

In short, document-relative coordinates pageX/Y are counted from the left-upper corner of the document, and do not change when the page is scrolled, while clientX/Y are counted from the current window left-upper corner. When the page is scrolled, they change.

For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then clientX and clientY are 0, no matter how the page is scrolled.

And if the mouse is in the center, then clientX and clientY are 250, no matter what place in the document it is. They are similar to position:fixed in that aspect.

    Move the mouse over the input field to see `clientX/clientY` (the example is in the `iframe`, so coordinates are relative to that `iframe`):
autorun height=50
<input onmousemove="this.value=event.clientX+':'+event.clientY" value="Mouse over me">

  

Preventing selection on mousedown

Double mouse click has a side-effect that may be disturbing in some interfaces: it selects text.

For instance, double-clicking on the text below selects it in addition to our handler:

html autorun height=50 <span ondblclick="alert('dblclick')">Double-click me</span>

If one presses the left mouse button and, without releasing it, moves the mouse, that also makes the selection, often unwanted.

There are multiple ways to prevent the selection, that you can read in the chapter info:selection-range.

In this particular case the most reasonable way is to prevent the browser action on mousedown. It prevents both these selections:

html autorun height=50 Before... <b ondblclick="alert('Click!')" *!*onmousedown="return false"*/!*> Double-click me </b> ...After

Now the bold element is not selected on double clicks, and pressing the left button on it won't start the selection.

Please note: the text inside it is still selectable. However, the selection should start not on the text itself, but before or after it. Usually that's fine for users.

smart header="Preventing copying" If we want to disable selection to protect our page content from copy-pasting, then we can use another event:oncopy`.

html autorun height=80 no-beautify <div *!*oncopy="alert('Copying forbidden!');return false"*/!*> Dear user, The copying is forbidden for you. If you know JS or HTML, then you can get everything from the page source though. </div> If you try to copy a piece of text in the <div>, that won't work, because the default action oncopy is prevented.

Surely the user has access to HTML-source of the page, and can take the content from there, but not everyone knows how to do it.

Summary

Mouse events have the following properties:

The default browser action of mousedown is text selection, if it's not good for the interface, then it should be prevented.

In the next chapter we'll see more details about events that follow pointer movement and how to track element changes under it.

Type Conversions

Most of the time, operators and functions automatically convert the values given to them to the right type.

For example, alert automatically converts any value to a string to show it. Mathematical operations convert values to numbers.

There are also cases when we need to explicitly convert a value to the expected type.

smart header="Not talking about objects yet" In this chapter, we won't cover objects. For now we'll just be talking about primitives.

Later, after we learn about objects, in the chapter info:object-toprimitive we'll see how objects fit in.

String Conversion

String conversion happens when we need the string form of a value.

For example, alert(value) does it to show the value.

We can also call the String(value) function to convert a value to a string:

run let value = true; alert(typeof value); // boolean

! value = String(value); // now value is a string "true" alert(typeof value); // string /!

String conversion is mostly obvious. A false becomes "false", null becomes "null", etc.

Numeric Conversion

Numeric conversion happens in mathematical functions and expressions automatically.

For example, when division / is applied to non-numbers:

js run alert( "6" / "2" ); // 3, strings are converted to numbers

We can use the Number(value) function to explicitly convert a value to a number:

run let str = "123"; alert(typeof str); // string

let num = Number(str); // becomes a number 123

alert(typeof num); // number

Explicit conversion is usually required when we read a value from a string-based source like a text form but expect a number to be entered.

If the string is not a valid number, the result of such a conversion is NaN. For instance:

run let age = Number("an arbitrary string instead of a number");

alert(age); // NaN, conversion failed

Numeric conversion rules:

Value Becomes…
undefined NaN
null 0
true and false 1 and 0
string Whitespaces from the start and end are removed. If the remaining string is empty, the result is 0. Otherwise, the number is "read" from the string. An error gives NaN.

Examples:

js run alert( Number(" 123 ") ); // 123 alert( Number("123z") ); // NaN (error reading a number at "z") alert( Number(true) ); // 1 alert( Number(false) ); // 0

Please note that null and undefined behave differently here: null becomes zero while undefined becomes NaN.

Most mathematical operators also perform such conversion, we'll see that in the next chapter.

Boolean Conversion

Boolean conversion is the simplest one.

It happens in logical operations (later we'll meet condition tests and other similar things) but can also be performed explicitly with a call to Boolean(value).

The conversion rule:

For instance:

run alert( Boolean(1) ); // true alert( Boolean(0) ); // false

alert( Boolean("hello") ); // true alert( Boolean("") ); // false

warn header="Please note: the string with zero"0" istrue " Some languages (namely PHP) treat"0" asfalse . But in JavaScript, a non-empty string is alwaystrue`.

js run alert( Boolean("0") ); // true alert( Boolean(" ") ); // spaces, also true (any non-empty string is true)

Summary

The three most widely used type conversions are to string, to number, and to boolean.

String Conversion - Occurs when we output something. Can be performed with String(value). The conversion to string is usually obvious for primitive values.

Numeric Conversion - Occurs in math operations. Can be performed with Number(value).

The conversion follows the rules:

Value Becomes…
undefined NaN
null 0
true / false 1 / 0
string The string is read "as is", whitespaces from both sides are ignored. An empty string becomes 0. An error gives NaN.

Boolean Conversion - Occurs in logical operations. Can be performed with Boolean(value).

Follows the rules:

Value Becomes…
0, null, undefined, NaN, "" false
any other value true

Most of these rules are easy to understand and memorize. The notable exceptions where people usually make mistakes are:

Objects aren't covered here. We'll return to them later in the chapter info:object-toprimitive that is devoted exclusively to objects after we learn more basic things about JavaScript.

Moving the mouse: mouseover/out, mouseenter/leave

Let's dive into more details about events that happen when the mouse moves between elements.

Events mouseover/mouseout, relatedTarget

The mouseover event occurs when a mouse pointer comes over an element, and mouseout - when it leaves.

These events are special, because they have property relatedTarget. This property complements target. When a mouse leaves one element for another, one of them becomes target, and the other one - relatedTarget.

For mouseover:

For mouseout the reverse:

    In the example below each face and its features are separate elements. When you move the mouse, you can see mouse events in the text area.
Each event has the information about both `target` and `relatedTarget`:
[codetabs src="mouseoverout" height=280]
  

`` warn header="relatedTarget can benull " TherelatedTarget property can benull`.

That's normal and just means that the mouse came not from another element, but from out of the window. Or that it left the window.

We should keep that possibility in mind when using event.relatedTarget in our code. If we access event.relatedTarget.tagName, then there will be an error.

Skipping elements

The mousemove event triggers when the mouse moves. But that doesn't mean that every pixel leads to an event.

The browser checks the mouse position from time to time. And if it notices changes then triggers the events.

That means that if the visitor is moving the mouse very fast then some DOM-elements may be skipped:

If the mouse moves very fast from #FROM to #TO elements as painted above, then intermediate <div> elements (or some of them) may be skipped. The mouseout event may trigger on #FROM and then immediately mouseover on #TO.

That's good for performance, because there may be many intermediate elements. We don't really want to process in and out of each one.

On the other hand, we should keep in mind that the mouse pointer doesn't "visit" all elements along the way. It can "jump".

In particular, it's possible that the pointer jumps right inside the middle of the page from out of the window. In that case relatedTarget is null, because it came from "nowhere":

    You can check it out "live" on a teststand below.
Its HTML has two nested elements: the `<div id="child">` is inside the `<div id="parent">`. If you move the mouse fast over them, then maybe only the child div triggers events, or maybe the parent one, or maybe there will be no events at all.
Also move the pointer into the child `div`, and then move it out quickly down through the parent one. If the movement is fast enough, then the parent element is ignored. The mouse will cross the parent element without noticing it.
[codetabs height=360 src="mouseoverout-fast"]
  

smart header="If `mouseover` triggered, there must be `mouseout`" In case of fast mouse movements, intermediate elements may be ignored, but one thing we know for sure: if the pointer "officially" entered an element (`mouseover` event generated), then upon leaving it we always get `mouseout`.

Mouseout when leaving for a child

An important feature of mouseout - it triggers, when the pointer moves from an element to its descendant, e.g. from #parent to #child in this HTML:

      
        
          <div
           id=
          "parent"
          >
        
        
          <div
           id=
          "child"
          >...
          </div>
        
        
          </div>
        
      
    

If we're on #parent and then move the pointer deeper into #child, we get mouseout on #parent!

That may seem strange, but can be easily explained.

According to the browser logic, the mouse cursor may be only over a single element at any time - the most nested one and top by z-index.

So if it goes to another element (even a descendant), then it leaves the previous one.

Please note another important detail of event processing.

The mouseover event on a descendant bubbles up. So, if #parent has mouseover handler, it triggers:

    You can see that very well in the example below: `<div id="child">` is inside the `<div id="parent">`. There are `mouseover/out` handlers on `#parent` element that output event details.
If you move the mouse from `#parent` to `#child`, you see two events on `#parent`:
1. `mouseout [target: parent]` (left the parent), then
2. `mouseover [target: child]` (came to the child, bubbled).
[codetabs height=360 src="mouseoverout-child"]
  

As shown, when the pointer moves from #parent element to #child, two handlers trigger on the parent element: mouseout and mouseover:

      
        
          parent.
          onmouseout
          =
          function(event) 
          {
        
        
          /* event.target: parent element */
        
        
          };
        
        
          parent.
          onmouseover
          =
          function(event) 
          {
        
        
          /* event.target: child element (bubbled) */
        
        
          };
        
      
    

If we don't examine event.target inside the handlers, then it may seem that the mouse pointer left #parent element, and then immediately came back over it.

But that's not the case! The pointer is still over the parent, it just moved deeper into the child element.

If there are some actions upon leaving the parent element, e.g. an animation runs in parent.onmouseout, we usually don't want it when the pointer just goes deeper into #parent.

To avoid it, we can check relatedTarget in the handler and, if the mouse is still inside the element, then ignore such event.

Alternatively we can use other events: mouseenter and mouseleave, that we'll be covering now, as they don't have such problems.

Events mouseenter and mouseleave

Events mouseenter/mouseleave are like mouseover/mouseout. They trigger when the mouse pointer enters/leaves the element.

But there are two important differences:

  1. Transitions inside the element, to/from descendants, are not counted.
  2. Events mouseenter/mouseleave do not bubble.

These events are extremely simple.

When the pointer enters an element - mouseenter triggers. The exact location of the pointer inside the element or its descendants doesn't matter.

When the pointer leaves an element - mouseleave triggers.

    This example is similar to the one above, but now the top element has `mouseenter/mouseleave` instead of `mouseover/mouseout`.
As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignored
[codetabs height=340 src="mouseleave"]
  

Event delegation

Events mouseenter/leave are very simple and easy to use. But they do not bubble. So we can't use event delegation with them.

Imagine we want to handle mouse enter/leave for table cells. And there are hundreds of cells.

The natural solution would be - to set the handler on <table> and process events there. But mouseenter/leave don't bubble. So if such event happens on <td>, then only a handler on that <td> is able to catch it.

Handlers for mouseenter/leave on <table> only trigger when the pointer enters/leaves the table as a whole. It's impossible to get any information about transitions inside it.

So, let's use mouseover/mouseout.

Let's start with simple handlers that highlight the element under mouse:

      
        
          // let's highlight an element under the pointer
        
        
          table.
          onmouseover
          =
          function(event) 
          {
        
        
          let target 
          =
          event.
          target
          ;
        
        
          target.
          style.
          background
          =
          'pink'
          ;
        
        
          };
        
        
        
          table.
          onmouseout
          =
          function(event) 
          {
        
        
          let target 
          =
          event.
          target
          ;
        
        
          target.
          style.
          background
          =
          ''
          ;
        
        
          };
        
      
    
    Here they are in action. As the mouse travels across the elements of this table, the current one is highlighted:
[codetabs height=480 src="mouseenter-mouseleave-delegation"]
  

In our case we'd like to handle transitions between table cells <td>: entering a cell and leaving it. Other transitions, such as inside the cell or outside of any cells, don't interest us. Let's filter them out.

Here's what we can do:

Here's an example of code that accounts for all possible situations:

[js src="mouseenter-mouseleave-delegation-2/script.js"]

Once again, the important features are: 1. It uses event delegation to handle entering/leaving of any <td> inside the table. So it relies on mouseover/out instead of mouseenter/leave that don't bubble and hence allow no delegation. 2. Extra events, such as moving between descendants of <td> are filtered out, so that onEnter/Leave runs only if the pointer leaves or enters <td> as a whole.

    Here's the full example with all details:
[codetabs height=460 src="mouseenter-mouseleave-delegation-2"]
Try to move the cursor in and out of table cells and inside them. Fast or slow -- doesn't matter. Only `<td>` as a whole is highlighted, unlike the example before.
  

Summary

We covered events mouseover, mouseout, mousemove, mouseenter and mouseleave.

These things are good to note:

Events mouseover/out trigger even when we go from the parent element to a child element. The browser assumes that the mouse can be only over one element at one time - the deepest one.

Events mouseenter/leave are different in that aspect: they only trigger when the mouse comes in and out the element as a whole. Also they do not bubble.

Drag'n'Drop with mouse events

Drag'n'Drop is a great interface solution. Taking something and dragging and dropping it is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (dropping items into a cart).

In the modern HTML standard there's a section about Drag and Drop with special events such as dragstart, dragend, and so on.

These events allow us to support special kinds of drag'n'drop, such as handling dragging a file from OS file-manager and dropping it into the browser window. Then JavaScript can access the contents of such files.

But native Drag Events also have limitations. For instance, we can't prevent dragging from a certain area. Also we can't make the dragging "horizontal" or "vertical" only. And there are many other drag'n'drop tasks that can't be done using them. Also, mobile device support for such events is very weak.

So here we'll see how to implement Drag'n'Drop using mouse events.

Drag'n'Drop algorithm

The basic Drag'n'Drop algorithm looks like this:

  1. On mousedown - prepare the element for moving, if needed (maybe create a clone of it, add a class to it or whatever).
  2. Then on mousemove move it by changing left/top with position:absolute.
  3. On mouseup - perform all actions related to finishing the drag'n'drop.

These are the basics. Later we'll see how to other features, such as highlighting current underlying elements while we drag over them.

Here's the implementation of dragging a ball:

      
        
          ball.
          onmousedown
          =
          function(event) 
          {
        
        
          // (1) prepare to moving: make absolute and on top by z-index
        
        
          ball.
          style.
          position
          =
          'absolute'
          ;
        
        
          ball.
          style.
          zIndex
          =
          1000
          ;
        
        
        
          // move it out of any current parents directly into body
        
        
          // to make it positioned relative to the body
        
        
          document.
          body.
          append(ball)
          ;
        
        
        
          // centers the ball at (pageX, pageY) coordinates
        
        
          function
          moveAt(pageX
          , pageY) 
          {
        
        
          ball.
          style.
          left
          = pageX 
          -
          ball.
          offsetWidth / 
          2
          +
          'px'
          ;
        
        
          ball.
          style.
          top
          = pageY 
          -
          ball.
          offsetHeight / 
          2
          +
          'px'
          ;
        
        
          }
        
        
        
          // move our absolutely positioned ball under the pointer
        
        
          moveAt(
          event.
          pageX
          ,
          event.
          pageY)
          ;
        
        
        
          function
          onMouseMove(event) 
          {
        
        
          moveAt(
          event.
          pageX
          ,
          event.
          pageY)
          ;
        
        
          }
        
        
        
          // (2) move the ball on mousemove
        
        
          document.
          addEventListener(
          'mousemove'
          , onMouseMove)
          ;
        
        
        
          // (3) drop the ball, remove unneeded handlers
        
        
          ball.
          onmouseup
          =
          function() 
          {
        
        
          document.
          removeEventListener(
          'mousemove'
          , onMouseMove)
          ;
        
        
          ball.
          onmouseup
          =
          null
          ;
        
        
          };
        
        
        
          };
        
      
    

If we run the code, we can notice something strange. On the beginning of the drag'n'drop, the ball "forks": we start dragging its "clone".

    Here's an example in action:
[iframe src="ball" height=230]
Try to drag'n'drop with the mouse and you'll see such behavior.
  

That's because the browser has its own drag'n'drop support for images and some other elements. It runs automatically and conflicts with ours.

To disable it:

      
        
          ball.
          ondragstart
          =
          function() 
          {
        
        
          return
          false
          ;
        
        
          };
        
      
    

Now everything will be all right.

    In action:
[iframe src="ball2" height=230]
  

Another important aspect - we track mousemove on document, not on ball. From the first sight it may seem that the mouse is always over the ball, and we can put mousemove on it.

But as we remember, mousemove triggers often, but not for every pixel. So after swift move the pointer can jump from the ball somewhere in the middle of document (or even outside of the window).

So we should listen on document to catch it.

Correct positioning

In the examples above the ball is always moved so, that it's center is under the pointer:

      
        
          ball.
          style.
          left
          = pageX 
          -
          ball.
          offsetWidth / 
          2
          +
          'px'
          ;
        
        
          ball.
          style.
          top
          = pageY 
          -
          ball.
          offsetHeight / 
          2
          +
          'px'
          ;
        
      
    

Not bad, but there's a side-effect. To initiate the drag'n'drop, we can mousedown anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer.

It would be better if we keep the initial shift of the element relative to the pointer.

For instance, if we start dragging by the edge of the ball, then the pointer should remain over the edge while dragging.

Let's update our algorithm:

  1. When a visitor presses the button ( mousedown) - remember the distance from the pointer to the left-upper corner of the ball in variables shiftX/shiftY. We'll keep that distance while dragging.

    To get these shifts we can substract the coordinates:

  2. Then while dragging we position the ball on the same shift relative to the pointer, like this:

The final code with better positioning:

      
        
          ball.
          onmousedown
          =
          function(event) 
          {
        
        
        
          *!*
        
        
          let shiftX 
          =
          event.
          clientX
          -
          ball.
          getBoundingClientRect().
          left
          ;
        
        
          let shiftY 
          =
          event.
          clientY
          -
          ball.
          getBoundingClientRect().
          top
          ;
        
        
          *
          /!
          *
        
        
        
            ball.style.position = 'absolute';
        
        
            ball.style.zIndex = 1000;
        
        
            document.body.append
          (
          ball
          )
          ;
        
        
        
            moveAt
          (
          event.pageX, event.pageY
          )
          ;
        
        
        
            // moves the ball at 
          (
          pageX, pageY
          )
           coordinates
        
        
            // taking initial shifts into account
        
        
            function moveAt
          (
          pageX, pageY
          )
           {
        
        
              ball.style.left = pageX - 
          *
          !
          *
          shiftX
          *
          /
          !*
          +
          'px'
          ;
        
        
          ball.
          style.
          top
          = pageY 
          -
          *!*shiftY
          *
          /!
          *
          
          +
           'px';
        
        
            }
        
        
        
            function onMouseMove
          (
          event
          )
           {
        
        
              moveAt
          (
          event.pageX, event.pageY
          )
          ;
        
        
            }
        
        
        
            // move the ball on mousemove
        
        
            document.addEventListener
          (
          'mousemove', onMouseMove
          )
          ;
        
        
        
            // drop the ball, remove unneeded handlers
        
        
            ball.onmouseup = function
          ()
           {
        
        
              document.removeEventListener
          (
          'mousemove', onMouseMove
          )
          ;
        
        
              ball.onmouseup = null;
        
        
            };
        
        
        
          };
        
        
        
          ball.ondragstart = function
          ()
           {
        
        
            return false;
        
        
          };
        
      
    
    In action (inside `<iframe>`):
[iframe src="ball3" height=230]
  

The difference is especially noticeable if we drag the ball by its right-bottom corner. In the previous example the ball "jumps" under the pointer. Now it fluently follows the pointer from the current position.

Potential drop targets (droppables)

In previous examples the ball could be dropped just "anywhere" to stay. In real-life we usually take one element and drop it onto another. For instance, a "file" into a "folder" or something else.

Speaking abstract, we take a "draggable" element and drop it onto "droppable" element.

We need to know: - where the element was dropped at the end of Drag'n'Drop - to do the corresponding action, - and, preferably, know the droppable we're dragging over, to highlight it.

The solution is kind-of interesting and just a little bit tricky, so let's cover it here.

What may be the first idea? Probably to set mouseover/mouseup handlers on potential droppables?

But that doesn't work.

The problem is that, while we're dragging, the draggable element is always above other elements. And mouse events only happen on the top element, not on those below it.

For instance, below are two <div> elements, red one on top of the blue one (fully covers). There's no way to catch an event on the blue one, because the red is on top:

html run autorun height=60 <style> div { width: 50px; height: 50px; position: absolute; top: 0; } </style> <div style="background:blue" onmouseover="alert('never works')"></div> <div style="background:red" onmouseover="alert('over red!')"></div>

The same with a draggable element. The ball is always on top over other elements, so events happen on it. Whatever handlers we set on lower elements, they won't work.

That's why the initial idea to put handlers on potential droppables doesn't work in practice. They won't run.

So, what to do?

There's a method called document.elementFromPoint(clientX, clientY). It returns the most nested element on given window-relative coordinates (or null if given coordinates are out of the window).

We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this:

      
        
          // in a mouse event handler
        
        
          ball.
          hidden
          =
          true
          ;
          // (*) hide the element that we drag
        
        
        
          let elemBelow 
          =
          document.
          elementFromPoint(
          event.
          clientX
          ,
          event.
          clientY)
          ;
        
        
          // elemBelow is the element below the ball, may be droppable
        
        
        
          ball.
          hidden
          =
          false
          ;
        
      
    

Please note: we need to hide the ball before the call (*). Otherwise we'll usually have a ball on these coordinates, as it's the top element under the pointer: elemBelow=ball. So we hide it and immediately show again.

We can use that code to check what element we're "flying over" at any time. And handle the drop when it happens.

An extended code of onMouseMove to find "droppable" elements:

      
        
          // potential droppable that we're flying over right now
        
        
          let currentDroppable 
          =
          null
          ;
        
        
        
          function
          onMouseMove(event) 
          {
        
        
          moveAt(
          event.
          pageX
          ,
          event.
          pageY)
          ;
        
        
        
          ball.
          hidden
          =
          true
          ;
        
        
          let elemBelow 
          =
          document.
          elementFromPoint(
          event.
          clientX
          ,
          event.
          clientY)
          ;
        
        
          ball.
          hidden
          =
          false
          ;
        
        
        
          // mousemove events may trigger out of the window (when the ball is dragged off-screen)
        
        
          // if clientX/clientY are out of the window, then elementFromPoint returns null
        
        
          if (
          !elemBelow) 
          return
          ;
        
        
        
          // potential droppables are labeled with the class "droppable" (can be other logic)
        
        
          let droppableBelow 
          =
          elemBelow.
          closest(
          '.droppable')
          ;
        
        
        
          if (currentDroppable 
          != droppableBelow) 
          {
        
        
          // we're flying in or out...
        
        
          // note: both values can be null
        
        
          //   currentDroppable=null if we were not over a droppable before this event (e.g over an empty space)
        
        
          //   droppableBelow=null if we're not over a droppable now, during this event
        
        
        
          if (currentDroppable) 
          {
        
        
          // the logic to process "flying out" of the droppable (remove highlight)
        
        
          leaveDroppable(currentDroppable)
          ;
        
        
          }
        
            currentDroppable 
          = droppableBelow
          ;
        
        
          if (currentDroppable) 
          {
        
        
          // the logic to process "flying in" of the droppable
        
        
          enterDroppable(currentDroppable)
          ;
        
        
          }
        
        
          }
        
        
          }
        
      
    

In the example below when the ball is dragged over the soccer goal, the goal is highlighted.

[codetabs height=250 src="ball4"]

Now we have the current "drop target", that we're flying over, in the variable currentDroppable during the whole process and can use it to highlight or any other stuff.

Summary

We considered a basic Drag'n'Drop algorithm.

The key components:

  1. Events flow: ball.mousedown -> document.mousemove -> ball.mouseup (don't forget to cancel native ondragstart).
  2. At the drag start - remember the initial shift of the pointer relative to the element: shiftX/shiftY and keep it during the dragging.
  3. Detect droppable elements under the pointer using document.elementFromPoint.

We can lay a lot on this foundation.

There are frameworks that build architecture over it: DragZone, Droppable, Draggable and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution.

Pointer events

Pointer events are a modern way to handle input from a variety of pointing devices, such as a mouse, a pen/stylus, a touchscreen, and so on.

The brief history

Let's make a small overview, so that you understand the general picture and the place of Pointer Events among other event types.

As of now, Pointer Events Level 2 specification is supported in all major browsers, while the newer Pointer Events Level 3 is in the works and is mostly compartible with Pointer Events level 2.

Unless you develop for old browsers, such as Internet Explorer 10, or for Safari 12 or below, there's no point in using mouse or touch events any more - we can switch to pointer events.

Then your code will work well with both touch and mouse devices.

That said, there are some important peculiarities that one should know in order to use Pointer Events correctly and avoid surprises. We'll make note of them in this article.

Pointer event types

Pointer events are named similarly to mouse events:

Pointer event Similar mouse event
pointerdown mousedown
pointerup mouseup
pointermove mousemove
pointerover mouseover
pointerout mouseout
pointerenter mouseenter
pointerleave mouseleave
pointercancel -
gotpointercapture -
lostpointercapture -

As we can see, for every mouse<event>, there's a pointer<event> that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding mouse... counterpart, we'll explain them soon.

`` smart header="Replacingmouse withpointer in our code" We can replacemouse events withpointer ` in our code and expect things to continue working fine with mouse.

The support for touch devices will also "magically" improve. Although, we may need to add touch-action: none in some places in CSS. We'll cover it below in the section about pointercancel.

Pointer event properties

Pointer events have the same properties as mouse events, such as clientX/Y, target, etc., plus some others:

Some pointer devices measure contact area and pressure, e.g. for a finger on the touchscreen, there are additional properties for that:

These properties aren't supported by most devices, so they are rarely used. You can find the details about them in the specification if needed.

Multi-touch

One of the things that mouse events totally don't support is multi-touch: a user can touch in several places at once on their phone or tablet, or perform special gestures.

Pointer Events allow handling multi-touch with the help of the pointerId and isPrimary properties.

Here's what happens when a user touches a touchscreen in one place, then puts another finger somewhere else on it:

  1. At the first finger touch:
  2. For the second finger and more fingers (assuming the first one is still touching):

Please note: the pointerId is assigned not to the whole device, but for each touching finger. If we use 5 fingers to simultaneously touch the screen, we have 5 pointerdown events, each with their respective coordinates and a different pointerId.

The events associated with the first finger always have isPrimary=true.

We can track multiple touching fingers using their pointerId. When the user moves and then removes a finger, we get pointermove and pointerup events with the same pointerId as we had in pointerdown.

            Here's the demo that logs `pointerdown` and `pointerup` events:
[iframe src="multitouch" edit height=200]
Please note: you must be using a touchscreen device, such as a phone or a tablet, to actually see the difference in `pointerId/isPrimary`. For single-touch devices, such as a mouse, there'll be always same `pointerId` with `isPrimary=true`, for all pointer events.
          

Event: pointercancel

The pointercancel event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated.

Such causes are: - The pointer device hardware was physically disabled. - The device orientation changed (tablet rotated). - The browser decided to handle the interaction on its own, considering it a mouse gesture or zoom-and-pan action or something else.

We'll demonstrate pointercancel on a practical example to see how it affects us.

Let's say we're impelementing drag'n'drop for a ball, just as in the beginning of the article info:mouse-drag-and-drop.

Here is the flow of user actions and the corresponding events:

  1. The user presses on an image, to start dragging
  2. Then they start moving the pointer (thus dragging the image)
  3. And then the surprise happens! The browser has native drag'n'drop support for images, that kicks in and takes over the drag'n'drop process, thus generating pointercancel event.

So the issue is that the browser "hijacks" the interaction: pointercancel fires in the beginning of the "drag-and-drop" process, and no more pointermove events are generated.

            Here's the drag'n'drop demo with loggin of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: 
[iframe src="ball" height=240 edit]
          

We'd like to implement the drag'n'drop on our own, so let's tell the browser not to take it over.

Prevent the default browser action to avoid pointercancel.

We need to do two things:

  1. Prevent native drag'n'drop from happening:
  2. For touch devices, there are other touch-related browser actions (besides drag'n'drop). To avoid problems with them too:

After we do that, the events will work as intended, the browser won't hijack the process and doesn't emit pointercancel.

            This demo adds these lines:
[iframe src="ball-2" height=240 edit]
As you can see, there's no `pointercancel` any more.
          

Now we can add the code to actually move the ball, and our drag'n'drop will work for mouse devices and touch devices.

Pointer capturing

Pointer capturing is a special feature of pointer events.

The idea is very simple, but may seem quite odd at first, as nothing like that exists for any other event type.

The main method is: - elem.setPointerCapture(pointerId) - binds events with the given pointerId to elem. After the call all pointer events with the same pointerId will have elem as the target (as if happened on elem), no matter where in document they really happened.

In other words, elem.setPointerCapture(pointerId) retargets all subsequent events with the given pointerId to elem.

The binding is removed: - automatically when pointerup or pointercancel events occur, - automatically when elem is removed from the document, - when elem.releasePointerCapture(pointerId) is called.

Pointer capturing can be used to simplify drag'n'drop kind of interactions.

As an example, let's recall how one can implement a custom slider, described in the info:mouse-drag-and-drop.

We make a slider element with the strip and the "runner" ( thumb) inside it.

Then it works like this:

  1. The user presses on the slider thumb - pointerdown triggers.
  2. Then they move the pointer - pointermove triggers, and we move the thumb along.

So, to track all pointer movements, including when it goes above/below the thumb, we had to assign pointermove event handler on the whole document.

That solution looks a bit "dirty". One of the problems is that pointer movements around the document may cause side effects, trigger other event handlers, totally not related to the slider.

Pointer capturing provides a means to bind pointermove to thumb and avoid any such problems:

So, even if the user moves the pointer around the whole document, events handlers will be called on thumb. Besides, coordinate properties of the event objects, such as clientX/clientY will still be correct - the capturing only affects target/currentTarget.

Here's the essential code:

              
                
                  thumb.
                  onpointerdown
                  =
                  function(event) 
                  {
                
                
                  // retarget all pointer events (until pointerup) to thumb
                
                
                  thumb.
                  setPointerCapture(
                  event.
                  pointerId)
                  ;
                
                
                  };
                
                
                
                  thumb.
                  onpointermove
                  =
                  function(event) 
                  {
                
                
                  // moving the slider: listen on the thumb, as all pointer events are retargeted to it
                
                
                  let newLeft 
                  =
                  event.
                  clientX
                  -
                  slider.
                  getBoundingClientRect().
                  left
                  ;
                
                
                  thumb.
                  style.
                  left
                  = newLeft 
                  +
                  'px'
                  ;
                
                
                  };
                
                
                
                  // note: no need to call thumb.releasePointerCapture, 
                
                
                  // it happens on pointerup automatically
                
              
            
            The full demo:
[iframe src="slider" height=100 edit]
          

At the end, pointer capturing gives us two benefits: 1. The code becomes cleaner as we don't need to add/remove handlers on the whole document any more. The binding is released automatically. 2. If there are any pointermove handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider.

Pointer capturing events

There are two associated pointer events:

Summary

Pointer events allow handling mouse, touch and pen events simultaneously, with a single piece of code.

Pointer events extend mouse events. We can replace mouse with pointer in event names and expect our code to continue working for mouse, with better support for other device types.

For drag'n'drops and complex touch interactions that the browser may decide to hijack and handle on its own - remember to cancel the default action on events and set touch-events: none in CSS for elements that we engage.

Additional abilities of pointer events are:

As of now, pointer events are supported in all major browsers, so we can safely switch to them, especially if IE10- and Safari 12- are not needed. And even with those browsers, there are polyfills that enable the support of pointer events.

Keyboard: keydown and keyup

Before we get to keyboard, please note that on modern devices there are other ways to "input something". For instance, people use speech recognition (especially on mobile devices) or copy/paste with the mouse.

So if we want to track any input into an <input> field, then keyboard events are not enough. There's another event named input to track changes of an <input> field, by any means. And it may be a better choice for such task. We'll cover it later in the chapter info:events-change-input.

Keyboard events should be used when we want to handle keyboard actions (virtual keyboard also counts). For instance, to react on arrow keys key:Up and key:Down or hotkeys (including combinations of keys).

Teststand [#keyboard-test-stand]

            To better understand keyboard events, you can use the [teststand](sandbox:keyboard-dump).
          
            To better understand keyboard events, you can use the teststand below.
Try different key combinations in the text field.
[codetabs src="keyboard-dump" height=480]
          

Keydown and keyup

The keydown events happens when a key is pressed down, and then keyup - when it's released.

event.code and event.key

The key property of the event object allows to get the character, while the code property of the event object allows to get the "physical key code".

For instance, the same key key:Z can be pressed with or without key:Shift. That gives us two different characters: lowercase z and uppercase Z.

The event.key is exactly the character, and it will be different. But event.code is the same:

Key event.key event.code
key:Z z (lowercase) KeyZ
key:Shift+Z Z (uppercase) KeyZ

If a user works with different languages, then switching to another language would make a totally different character instead of "Z". That will become the value of event.key, while event.code is always the same: "KeyZ".

smart header=""KeyZ" and other key codes" Every key has the code that depends on its location on the keyboard. Key codes described in the UI Events code specification.

For instance: - Letter keys have codes "Key<letter>": "KeyA", "KeyB" etc. - Digit keys have codes: "Digit<number>": "Digit0", "Digit1" etc. - Special keys are coded by their names: "Enter", "Backspace", "Tab" etc.

There are several widespread keyboard layouts, and the specification gives key codes for each of them.

Read the alphanumeric section of the spec for more codes, or just press a key in the teststand above.

`` warn header="Case matters:"KeyZ" , not"keyZ"`" Seems obvious, but people still make mistakes.

Please evade mistypes: it's KeyZ, not keyZ. The check like event.code=="keyZ" won't work: the first letter of "Key" must be uppercase.

What if a key does not give any character? For instance, key:Shift or key:F1 or others. For those keys, event.key is approximately the same as event.code:

Key event.key event.code
key:F1 F1 F1
key:Backspace Backspace Backspace
key:Shift Shift ShiftRight or ShiftLeft

Please note that event.code specifies exactly which key is pressed. For instance, most keyboards have two key:Shift keys: on the left and on the right side. The event.code tells us exactly which one was pressed, and event.key is responsible for the "meaning" of the key: what it is (a "Shift").

Let's say, we want to handle a hotkey: key:Ctrl+Z (or key:Cmd+Z for Mac). Most text editors hook the "Undo" action on it. We can set a listener on keydown and check which key is pressed.

There's a dilemma here: in such a listener, should we check the value of event.key or event.code?

On one hand, the value of event.key is a character, it changes depending on the language. If the visitor has several languages in OS and switches between them, the same key gives different characters. So it makes sense to check event.code, it's always the same.

Like this:

js run document.addEventListener('keydown', function(event) { if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) { alert('Undo!') } });

On the other hand, there's a problem with event.code. For different keyboard layouts, the same key may have different characters.

For example, here are US layout ("QWERTY") and German layout ("QWERTZ") under it (from Wikipedia):

For the same key, US layout has "Z", while German layout has "Y" (letters are swapped).

Literally, event.code will equal KeyZ for people with German layout when they press key:Y.

If we check event.code == 'KeyZ' in our code, then for people with German layout such test will pass when they press key:Y.

That sounds really odd, but so it is. The specification explicitly mentions such behavior.

So, event.code may match a wrong character for unexpected layout. Same letters in different layouts may map to different physical keys, leading to different codes. Luckily, that happens only with several codes, e.g. keyA, keyQ, keyZ (as we've seen), and doesn't happen with special keys such as Shift. You can find the list in the specification.

To reliably track layout-dependent characters, event.key may be a better way.

On the other hand, event.code has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch.

Do we want to handle layout-dependant keys? Then event.key is the way to go.

Or we want a hotkey to work even after a language switch? Then event.code may be better.

Auto-repeat

If a key is being pressed for a long enough time, it starts to "auto-repeat": the keydown triggers again and again, and then when it's released we finally get keyup. So it's kind of normal to have many keydown and a single keyup.

For events triggered by auto-repeat, the event object has event.repeat property set to true.

Default actions

Default actions vary, as there are many possible things that may be initiated by the keyboard.

For instance:

Preventing the default action on keydown can cancel most of them, with the exception of OS-based special keys. For instance, on Windows key:Alt+F4 closes the current browser window. And there's no way to stop it by preventing the default action in JavaScript.

For instance, the <input> below expects a phone number, so it does not accept keys except digits, +, () or -:

html autorun height=60 run <script> function checkPhoneKey(key) { return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-'; } </script> <input *!*onkeydown="return checkPhoneKey(event.key)"*/!* placeholder="Phone, please" type="tel">

Please note that special keys, such as key:Backspace, key:Left, key:Right, key:Ctrl+V, do not work in the input. That's a side-effect of the strict filter checkPhoneKey.

Let's relax it a little bit:

html autorun height=60 run <script> function checkPhoneKey(key) { return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' || key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace'; } </script> <input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

Now arrows and deletion works well.

…But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the input event - it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid.

Legacy

In the past, there was a keypress event, and also keyCode, charCode, which properties of the event object.

There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more.

Mobile Keyboards

When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's e.keyCode should be 229 and e.key should be "Unidentified" .

While some of these keyboards might still use the right values for e.key, e.code, e.keyCode… when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices.

Summary

Pressing a key always generates a keyboard event, be it symbol keys or special keys like key:Shift or key:Ctrl and so on. The only exception is key:Fn key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS.

Keyboard events:

Main keyboard event properties:

In the past, keyboard events were sometimes used to track user input in form fields. That's not reliable, because the input can come from various sources. We have input and change events to handle any input (covered later in the chapter info:events-change-input). They trigger after any kind of input, including copy-pasting or speech recognition.

We should use keyboard events when we really want keyboard. For example, to react on hotkeys or special keys.

Scrolling

The scroll event allows reacting to a page or element scrolling. There are quite a few good things we can do here.

For instance: - Show/hide additional controls or information depending on where in the document the user is. - Load more data when the user scrolls down till the end of the page.

Here's a small function to show the current scroll:

js autorun window.addEventListener('scroll', function() { document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px'; });

            In action:
Current scroll = <b id="showScroll">scroll the window</b>
          

The scroll event works both on the window and on scrollable elements.

Prevent scrolling

How do we make something unscrollable?

We can't prevent scrolling by using event.preventDefault() in onscroll listener, because it triggers after the scroll has already happened.

But we can prevent scrolling by event.preventDefault() on an event that causes the scroll, for instance keydown event for key:pageUp and key:pageDown.

If we add an event handler to these events and event.preventDefault() in it, then the scroll won't start.

There are many ways to initiate a scroll, so it's more reliable to use CSS, overflow property.

Here are few tasks that you can solve or look through to see applications of onscroll.

Form properties and methods

Forms and control elements, such as <input> have a lot of special properties and events.

Working with forms will be much more convenient when we learn them.

Document forms are members of the special collection document.forms.

That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form.

js no-beautify document.forms.my; // the form with name="my" document.forms[0]; // the first form in the document

When we have a form, then any element is available in the named collection form.elements.

For instance:

run height=40

There may be multiple elements with the same name. This is typical with radio buttons and checkboxes.

In that case, form.elements[name] is a collection. For instance:

run height=40
<input type="radio" !name="age" /! value="10"> <input type="radio" !name="age" /! value="20">

These navigation properties do not depend on the tag structure. All control elements, no matter how deep they are in the form, are available in form.elements.

smart header="Fieldsets as \"subforms\"" A form may have one or many

elements inside it. They also haveelements` property that lists form controls inside them.

For instance:

run height=80
info
                          
                        

warn header="Shorter notation:form.name " There's a shorter notation: we can access the element asform[index/name]`.

In other words, instead of form.elements.login we can write form.login.

That also works, but there's a minor issue: if we access an element, and then change its name, then it is still available under the old name (as well as under the new one).

That's easy to see in an example:

run height=40
                              
That's usually not a problem, however, because we rarely change names of form elements.

                            

Backreference: element.form

For any element, the form is available as element.form. So a form references all elements, and elements reference the form.

Here's the picture:

For instance:

run height=40

Form elements

Let's talk about form controls.

input and textarea

We can access their value as input.value (string) or input.checked (boolean) for checkboxes.

Like this:

                                  
                                    
                                      input.
                                      value
                                      =
                                      "New value"
                                      ;
                                    
                                    
                                      textarea.
                                      value
                                      =
                                      "New text"
                                      ;
                                    
                                    
                                    
                                      input.
                                      checked
                                      =
                                      true
                                      ;
                                      // for a checkbox or radio button
                                    
                                  
                                
`` warn header="Usetextarea.value , nottextarea.innerHTML " Please note that even though

holds its value as nested HTML, we should never usetextarea.innerHTML` to access it.

It stores only the HTML that was initially on the page, not the current value.

select and option

A <select> element has 3 important properties:

  1. select.options - the collection of <option> subelements,
  2. select.value - the value of the currently selected <option>,
  3. select.selectedIndex - the number of the currently selected <option>.

They provide three different ways of setting a value for a <select>:

  1. Find the corresponding <option> element (e.g. among select.options) and set its option.selected to true.
  2. If we know a new value: set select.value to the new value.
  3. If we know the new option number: set select.selectedIndex to that number.

Here is an example of all three methods:

run

Unlike most other controls, <select> allows to select multiple options at once if it has multiple attribute. This attribute is rarely used, though.

For multiple selected values, use the first way of setting values: add/remove the selected property from <option> subelements.

Here's an example of how to get selected values from a multi-select:

run <select id="select" !multiple /!>

The full specification of the <select> element is available in the specification https://html.spec.whatwg.org/multipage/forms.html#the-select-element.

new Option

In the specification there's a nice short syntax to create an <option> element:

                                
                                  option 
                                    =
                                    new
                                    Option(text
                                    , value
                                    , defaultSelected
                                    , selected)
                                    ;
                                  
                                
                              

This syntax is optional. We can use document.createElement('option') and set attributes manually. Still, it may be shorter, so here are the parameters:

The difference between defaultSelected and selected is that defaultSelected sets the HTML-attribute (that we can get using option.getAttribute('selected'), while selected sets whether the option is selected or not.

In practice, one should usually set both values to true or false. (Or, simply omit them; both default to false.)

For instance, here's a new "unselected" option:

                                
                                  
                                    let option 
                                    =
                                    new
                                    Option(
                                    "Text"
                                    ,
                                    "value")
                                    ;
                                  
                                  
                                    // creates <option value="value">Text</option>
                                  
                                
                              

The same option, but selected:

                                
                                  
                                    let option 
                                    =
                                    new
                                    Option(
                                    "Text"
                                    ,
                                    "value"
                                    ,
                                    true
                                    ,
                                    true)
                                    ;
                                  
                                
                              

Option elements have properties:

option.selected
Is the option selected.
option.index
The number of the option among the others in its <select>.
option.text
Text content of the option (seen by the visitor).

References

Summary

Form navigation:

document.forms
A form is available as document.forms[name/index].
form.elements
Form elements are available as form.elements[name/index], or can use just form[name/index]. The elements property also works for <fieldset>.
element.form
Elements reference their form in the form property.

Value is available as input.value, textarea.value, select.value, etc. (For checkboxes and radio buttons, use input.checked to determine whether a value is selected.)

For <select>, one can also get the value by the index select.selectedIndex or through the options collection select.options.

These are the basics to start working with forms. We'll meet many examples further in the tutorial.

In the next chapter we'll cover focus and blur events that may occur on any element, but are mostly handled on forms.

Focusing: focus/blur

An element receives the focus when the user either clicks on it or uses the key:Tab key on the keyboard. There's also an autofocus HTML attribute that puts the focus onto an element by default when a page loads and other means of getting the focus.

Focusing on an element generally means: "prepare to accept the data here", so that's the moment when we can run the code to initialize the required functionality.

The moment of losing the focus ("blur") can be even more important. That's when a user clicks somewhere else or presses key:Tab to go to the next form field, or there are other means as well.

Losing the focus generally means: "the data has been entered", so we can run the code to check it or even to save it to the server and so on.

There are important peculiarities when working with focus events. We'll do the best to cover them further on.

Events focus/blur

The focus event is called on focusing, and blur - when the element loses the focus.

Let's use them for validation of an input field.

In the example below:

run autorun height=60

Your email please:

Modern HTML allows us to do many validations using input attributes: required, pattern and so on. And sometimes they are just what we need. JavaScript can be used when we want more flexibility. Also we could automatically send the changed value to the server if it's correct.

Methods focus/blur

Methods elem.focus() and elem.blur() set/unset the focus on the element.

For instance, let's make the visitor unable to leave the input if the value is invalid:

run autorun height=80

Your email please:

It works in all browsers except Firefox ( bug).

If we enter something into the input and then try to use key:Tab or click away from the <input>, then onblur returns the focus back.

Please note that we can't "prevent losing focus" by calling event.preventDefault() in onblur, because onblur works after the element lost the focus.

warn header="JavaScript-initiated focus loss" A focus loss can occur for many reasons.

One of them is when the visitor clicks somewhere else. But also JavaScript itself may cause it, for instance:

These features sometimes cause focus/blur handlers to misbehave - to trigger when they are not needed.

The best recipe is to be careful when using these events. If we want to track user-initiated focus-loss, then we should avoid causing it ourselves. ## Allow focusing on any element: tabindex

By default many elements do not support focusing.

The list varies a bit between browsers, but one thing is always correct: focus/blur support is guaranteed for elements that a visitor can interact with: <button>, <input>, <select>, <a> and so on.

On the other hand, elements that exist to format something, such as <div>, <span>, <table> - are unfocusable by default. The method elem.focus() doesn't work on them, and focus/blur events are never triggered.

This can be changed using HTML-attribute tabindex.

Any element becomes focusable if it has tabindex. The value of the attribute is the order number of the element when key:Tab (or something like that) is used to switch between them.

That is: if we have two elements, the first has tabindex="1", and the second has tabindex="2", then pressing key:Tab while in the first element - moves the focus into the second one.

The switch order is: elements with tabindex from 1 and above go first (in the tabindex order), and then elements without tabindex (e.g. a regular <input>).

Elements with matching tabindex are switched in the document source order (the default order).

There are two special values:

For instance, here's a list. Click the first item and press key:Tab:

autorun no-beautify Click the first item and press Tab. Keep track of the order. Please note that many subsequent Tabs can move the focus out of the iframe in the example.

The order is like this: 1 - 2 - 0. Normally, <li> does not support focusing, but tabindex full enables it, along with events and styling with :focus.

smart header="The property `elem.tabIndex` works too" We can add `tabindex` from JavaScript by using the `elem.tabIndex` property. That has the same effect.

Delegation: focusin/focusout

Events focus and blur do not bubble.

For instance, we can't put onfocus on the <form> to highlight it, like this:

autorun height=80 <form !onfocus="this.className=‘focused'" /!>

The example above doesn't work, because when user focuses on an <input>, the focus event triggers on that input only. It doesn't bubble up. So form.onfocus never triggers.

There are two solutions.

First, there's a funny historical feature: focus/blur do not bubble up, but propagate down on the capturing phase.

This will work:

autorun height=80

Second, there are focusin and focusout events - exactly the same as focus/blur, but they bubble.

Note that they must be assigned using elem.addEventListener, not on<event>.

So here's another working variant:

autorun height=80

Summary

Events focus and blur trigger on an element focusing/losing focus.

Their specials are: - They do not bubble. Can use capturing state instead or focusin/focusout. - Most elements do not support focus by default. Use tabindex to make anything focusable.

The current focused element is available as document.activeElement.

Events: change, input, cut, copy, paste

Let's cover various events that accompany data updates.

Event: change

The change event triggers when the element has finished changing.

For text inputs that means that the event occurs when it loses focus.

For instance, while we are typing in the text field below - there's no event. But when we move the focus somewhere else, for instance, click on a button - there will be a change event:

html autorun height=40 run <input type="text" onchange="alert(this.value)"> <input type="button" value="Button">

For other elements: select, input type=checkbox/radio it triggers right after the selection changes:

html autorun height=40 run <select onchange="alert(this.value)"> <option value="">Select something</option> <option value="1">Option 1</option> <option value="2">Option 2</option> <option value="3">Option 3</option> </select>

Event: input

The input event triggers every time after a value is modified by the user.

Unlike keyboard events, it triggers on any value change, even those that does not involve keyboard actions: pasting with a mouse or using speech recognition to dictate the text.

For instance:

html autorun height=40 run <input type="text" id="input"> oninput: <span id="result"></span> <script> input.oninput = function() { result.innerHTML = input.value; }; </script>

If we want to handle every modification of an <input> then this event is the best choice.

On the other hand, input event doesn't trigger on keyboard input and other actions that do not involve value change, e.g. pressing arrow keys key:⇦ key:⇨ while in the input.

`` smart header="Can't prevent anything inoninput " Theinput` event occurs after the value is modified.

So we can't use event.preventDefault() there - it's just too late, there would be no effect.

Events: cut, copy, paste

These events occur on cutting/copying/pasting a value.

They belong to ClipboardEvent class and provide access to the data that is copied/pasted.

We also can use event.preventDefault() to abort the action, then nothing gets copied/pasted.

For instance, the code below prevents all such events and shows what we are trying to cut/copy/paste:

html autorun height=40 run <input type="text" id="input"> <script> input.oncut = input.oncopy = input.onpaste = function(event) { alert(event.type + ' - ' + event.clipboardData.getData('text/plain')); return false; }; </script>

Please note, that it's possible to copy/paste not just text, but everything. For instance, we can copy a file in the OS file manager, and paste it.

That's because clipboardData implements DataTransfer interface, commonly used for drag'n'drop and copy/pasting. It's bit beyound our scope now, but you can find its methods in the specification.

`` warn header="ClipboardAPI: user safety restrictions" The clipboard is a "global" OS-level thing. So most browsers allow read/write access to the clipboard only in the scope of certain user actions for the safety, e.g. inonclick` event handlers.

Also it's forbidden to generate "custom" clipboard events with dispatchEvent in all browsers except Firefox.

Summary

Data change events:

Event Description Specials
change A value was changed. For text inputs triggers on focus loss.
input For text inputs on every change. Triggers immediately unlike change.
cut/copy/paste Cut/copy/paste actions. The action can be prevented. The event.clipboardData property gives read/write access to the clipboard.

Forms: event and method submit

The submit event triggers when the form is submitted, it is usually used to validate the form before sending it to the server or to abort the submission and process it in JavaScript.

The method form.submit() allows to initiate form sending from JavaScript. We can use it to dynamically create and send our own forms to server.

Let's see more details of them.

Event: submit

There are two main ways to submit a form:

  1. The first - to click <input type="submit"> or <input type="image">.
  2. The second - press key:Enter on an input field.

Both actions lead to submit event on the form. The handler can check the data, and if there are errors, show them and call event.preventDefault(), then the form won't be sent to the server.

In the form below: 1. Go into the text field and press key:Enter. 2. Click <input type="submit">.

Both actions show alert and the form is not sent anywhere due to return false:

html autorun height=60 no-beautify <form onsubmit="alert('submit!');return false"> First: Enter in the input field <input type="text" value="text"><br> Second: Click "submit": <input type="submit" value="Submit"> </form>

smart header="Relation betweensubmit andclick " When a form is sent usingkey:Enter on an input field, aclick event triggers on the `.

That's rather funny, because there was no click at all.

Here's the demo: html autorun height=60 <form onsubmit="return false"> <input type="text" size="30" value="Focus here and press enter"> <input type="submit" value="Submit" *!*onclick="alert('click')"*/!*> </form>

Method: submit

To submit a form to the server manually, we can call form.submit().

Then the submit event is not generated. It is assumed that if the programmer calls form.submit(), then the script already did all related processing.

Sometimes that's used to manually create and send a form, like this:

run let form = document.createElement(‘form'); form.action = ‘https://google.com/search'; form.method = ‘GET';

form.innerHTML = ‘ ''';

// the form must be in the document to submit it document.body.append(form);

form.submit();

Page: DOMContentLoaded, load, beforeunload, unload

The lifecycle of an HTML page has three important events:

Each event may be useful:

Let's explore the details of these events.

DOMContentLoaded

The DOMContentLoaded event happens on the document object.

We must use addEventListener to catch it:

                                                    
                                                      
                                                        document.
                                                        addEventListener(
                                                        "DOMContentLoaded"
                                                        , ready)
                                                        ;
                                                      
                                                      
                                                        // not "document.onDOMContentLoaded = ..."
                                                      
                                                    
                                                  

For instance:

run height=200 refresh

In the example the DOMContentLoaded handler runs when the document is loaded, so it can see all the elements, including <img> below.

But it doesn't wait for the image to load. So alert shows zero sizes.

At first sight, the DOMContentLoaded event is very simple. The DOM tree is ready - here's the event. There are few peculiarities though.

DOMContentLoaded and scripts

When the browser processes an HTML-document and comes across a <script> tag, it needs to execute before continuing building the DOM. That's a precaution, as scripts may want to modify DOM, and even document.write into it, so DOMContentLoaded has to wait.

So DOMContentLoaded definitely happens after such scripts:

run

In the example above, we first see "Library loaded…", and then "DOM ready!" (all scripts are executed).

warn header="Scripts that don't block DOMContentLoaded" There are two exceptions from this rule: 1. Scripts with the `async` attribute, that we'll cover [a bit later](info:script-async-defer), don't block `DOMContentLoaded`. 2. Scripts that are generated dynamically with `document.createElement('script')` and then added to the webpage also don't block this event.

DOMContentLoaded and styles

External style sheets don't affect DOM, so DOMContentLoaded does not wait for them.

But there's a pitfall. If we have a script after the style, then that script must wait until the stylesheet loads:

html run <link type="text/css" rel="stylesheet" href="style.css"> <script> // the script doesn't not execute until the stylesheet is loaded alert(getComputedStyle(document.body).marginTop); </script>

The reason for this is that the script may want to get coordinates and other style-dependent properties of elements, like in the example above. Naturally, it has to wait for styles to load.

As DOMContentLoaded waits for scripts, it now waits for styles before them as well.

Built-in browser autofill

Firefox, Chrome and Opera autofill forms on DOMContentLoaded.

For instance, if the page has a form with login and password, and the browser remembered the values, then on DOMContentLoaded it may try to autofill them (if approved by the user).

So if DOMContentLoaded is postponed by long-loading scripts, then autofill also awaits. You probably saw that on some sites (if you use browser autofill) - the login/password fields don't get autofilled immediately, but there's a delay till the page fully loads. That's actually the delay until the DOMContentLoaded event.

window.onload [#window-onload]

The load event on the window object triggers when the whole page is loaded including styles, images and other resources. This event is available via the onload property.

The example below correctly shows image sizes, because window.onload waits for all images:

run height=200 refresh

window.onunload

When a visitor leaves the page, the unload event triggers on window. We can do something there that doesn't involve a delay, like closing related popup windows.

The notable exception is sending analytics.

Let's say we gather data about how the page is used: mouse clicks, scrolls, viewed page areas, and so on.

Naturally, unload event is when the user leaves us, and we'd like to save the data on our server.

There exists a special navigator.sendBeacon(url, data) method for such needs, described in the specification https://w3c.github.io/beacon/.

It sends the data in background. The transition to another page is not delayed: the browser leaves the page, but still performs sendBeacon.

Here's how to use it:

                                                        
                                                          
                                                            let analyticsData 
                                                            =
                                                            {
                                                            /* object with gathered data */
                                                            };
                                                          
                                                          
                                                          
                                                            window.
                                                            addEventListener(
                                                            "unload"
                                                            ,
                                                            function() 
                                                            {
                                                          
                                                          
                                                            navigator.
                                                            sendBeacon(
                                                            "/analytics"
                                                            ,
                                                            JSON.
                                                            stringify(analyticsData))
                                                            ;
                                                          
                                                          
                                                            })
                                                            ;
                                                          
                                                        
                                                      

When the sendBeacon request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics).

There's also a keepalive flag for doing such "after-page-left" requests in fetch method for generic network requests. You can find more information in the chapter info:fetch-api.

If we want to cancel the transition to another page, we can't do it here. But we can use another event - onbeforeunload.

window.onbeforeunload [#window.onbeforeunload]

If a visitor initiated navigation away from the page or tries to close the window, the beforeunload handler asks for additional confirmation.

If we cancel the event, the browser may ask the visitor if they are sure.

You can try it by running this code and then reloading the page:

js run window.onbeforeunload = function() { return false; };

For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used to show it as a message, but as the modern specification says, they shouldn't.

Here's an example:

js run window.onbeforeunload = function() { return "There are unsaved changes. Leave now?"; };

The behavior was changed, because some webmasters abused this event handler by showing misleading and annoying messages. So right now old browsers still may show it as a message, but aside of that - there's no way to customize the message shown to the user.

readyState

What happens if we set the DOMContentLoaded handler after the document is loaded?

Naturally, it never runs.

There are cases when we are not sure whether the document is ready or not. We'd like our function to execute when the DOM is loaded, be it now or later.

The document.readyState property tells us about the current loading state.

There are 3 possible values:

So we can check document.readyState and setup a handler or execute the code immediately if it's ready.

Like this:

                                                        
                                                          
                                                            function
                                                            work() 
                                                            {
                                                            /*...*/
                                                            }
                                                          
                                                          
                                                          
                                                            if (
                                                            document.
                                                            readyState
                                                            ==
                                                            'loading') 
                                                            {
                                                          
                                                          
                                                            // still loading, wait for the event
                                                          
                                                          
                                                            document.
                                                            addEventListener(
                                                            'DOMContentLoaded'
                                                            , work)
                                                            ;
                                                          
                                                          
                                                            }
                                                            else
                                                            {
                                                          
                                                          
                                                            // DOM is ready!
                                                          
                                                          
                                                            work()
                                                            ;
                                                          
                                                          
                                                            }
                                                          
                                                        
                                                      

There's also the readystatechange event that triggers when the state changes, so we can print all these states like this:

run // current state console.log(document.readyState);

// print state changes document.addEventListener(‘readystatechange', () => console.log(document.readyState));

The readystatechange event is an alternative mechanics of tracking the document loading state, it appeared long ago. Nowadays, it is rarely used.

Let's see the full events flow for the completeness.

Here's a document with <iframe>, <img> and handlers that log events:

                                                        
                                                          
                                                            <script>
                                                          
                                                          
                                                            log(
                                                            'initial readyState:'
                                                            +
                                                            document.
                                                            readyState)
                                                            ;
                                                          
                                                          
                                                          
                                                            document.
                                                            addEventListener(
                                                            'readystatechange'
                                                            , () 
                                                            =>
                                                            log(
                                                            'readyState:'
                                                            +
                                                            document.
                                                            readyState))
                                                            ;
                                                          
                                                          
                                                            document.
                                                            addEventListener(
                                                            'DOMContentLoaded'
                                                            , () 
                                                            =>
                                                            log(
                                                            'DOMContentLoaded'))
                                                            ;
                                                          
                                                          
                                                          
                                                            window.
                                                            onload
                                                            = () 
                                                            =>
                                                            log(
                                                            'window onload')
                                                            ;
                                                          
                                                          
                                                            </script>
                                                          
                                                          
                                                          
                                                            <iframe
                                                             src=
                                                            "iframe.html"
                                                             onload=
                                                            "log('iframe onload')"
                                                            ></iframe>
                                                          
                                                          
                                                          
                                                            <img
                                                             src=
                                                            "http://en.js.cx/clipart/train.gif"
                                                             id=
                                                            "img"
                                                            >
                                                          
                                                          
                                                            <script>
                                                          
                                                          
                                                            img.
                                                            onload
                                                            = () 
                                                            =>
                                                            log(
                                                            'img onload')
                                                            ;
                                                          
                                                          
                                                            </script>
                                                          
                                                        
                                                      

The working example is in the sandbox.

The typical output: 1. [1] initial readyState:loading 2. [2] readyState:interactive 3. [2] DOMContentLoaded 4. [3] iframe onload 5. [4] img onload 6. [4] readyState:complete 7. [4] window onload

The numbers in square brackets denote the approximate time of when it happens. Events labeled with the same digit happen approximately at the same time (+- a few ms).

Summary

Page load events:

Basic operators, maths

We know many operators from school. They are things like addition +, multiplication *, subtraction -, and so on.

In this chapter, we'll start with simple operators, then concentrate on JavaScript-specific aspects, not covered by school arithmetic.

Terms: "unary", "binary", "operand"

Before we move on, let's grasp some common terminology.

Maths

The following math operations are supported:

The first four are straightforward, while % and ** need a few words about them.

Remainder %

The remainder operator %, despite its appearance, is not related to percents.

The result of a % b is the remainder of the integer division of a by b.

For instance:

js run alert( 5 % 2 ); // 1, a remainder of 5 divided by 2 alert( 8 % 3 ); // 2, a remainder of 8 divided by 3

Exponentiation **

The exponentiation operator a ** b raises a to the power of b.

In school maths, we write that as a b.

For instance:

js run alert( 2 ** 2 ); // 2² = 4 alert( 2 ** 3 ); // 2³ = 8 alert( 2 ** 4 ); // 2⁴ = 16

Just like in maths, the exponentiation operator is defined for non-integer numbers as well.

For example, a square root is an exponentiation by ½:

js run alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)

String concatenation with binary +

Let's meet features of JavaScript operators that are beyond school arithmetics.

Usually, the plus operator + sums numbers.

But, if the binary + is applied to strings, it merges (concatenates) them:

                                                        
                                                          
                                                            let s 
                                                            =
                                                            "my"
                                                            +
                                                            "string"
                                                            ;
                                                          
                                                          
                                                            alert(s)
                                                            ;
                                                            // mystring
                                                          
                                                        
                                                      

Note that if any of the operands is a string, then the other one is converted to a string too.

For example:

js run alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21"

See, it doesn't matter whether the first operand is a string or the second one.

Here's a more complex example:

js run alert(2 + 2 + '1' ); // "41" and not "221"

Here, operators work one after another. The first + sums two numbers, so it returns 4, then the next + adds the string 1 to it, so it's like 4 + '1' = '41'.

js run alert('1' + 2 + 2); // "122" and not "14" Here, the first operand is a string, the compiler treats the other two operands as strings too. The 2 gets concatenated to '1', so it's like '1' + 2 = "12" and "12" + 2 = "122".

The binary + is the only operator that supports strings in such a way. Other arithmetic operators work only with numbers and always convert their operands to numbers.

Here's the demo for subtraction and division:

js run alert( 6 - '2' ); // 4, converts '2' to a number alert( '6' / '2' ); // 3, converts both operands to numbers

Numeric conversion, unary +

The plus + exists in two forms: the binary form that we used above and the unary form.

The unary plus or, in other words, the plus operator + applied to a single value, doesn't do anything to numbers. But if the operand is not a number, the unary plus converts it into a number.

For example:

run // No effect on numbers let x = 1; alert( +x ); // 1

let y = -2; alert( +y ); // -2

! // Converts non-numbers alert( +true ); // 1 alert( +"" ); // 0 /!

It actually does the same thing as Number(...), but is shorter.

The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. What if we want to sum them?

The binary plus would add them as strings:

run let apples = "2"; let oranges = "3";

alert( apples + oranges ); // "23", the binary plus concatenates strings

If we want to treat them as numbers, we need to convert and then sum them:

run let apples = "2"; let oranges = "3";

! // both values converted to numbers before the binary plus alert( +apples + +oranges ); // 5 /!

// the longer variant // alert( Number(apples) + Number(oranges) ); // 5

From a mathematician's standpoint, the abundance of pluses may seem strange. But from a programmer's standpoint, there's nothing special: unary pluses are applied first, they convert strings to numbers, and then the binary plus sums them up.

Why are unary pluses applied to values before the binary ones? As we're going to see, that's because of their higher precedence.

Operator precedence

If an expression has more than one operator, the execution order is defined by their precedence, or, in other words, the default priority order of operators.

From school, we all know that the multiplication in the expression 1 + 2 * 2 should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have a higher precedence than the addition.

Parentheses override any precedence, so if we're not satisfied with the default order, we can use them to change it. For example, write (1 + 2) * 2.

There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the larger number executes first. If the precedence is the same, the execution order is from left to right.

Here's an extract from the precedence table (you don't need to remember this, but note that unary operators are higher than corresponding binary ones):

Precedence Name Sign
17 unary plus +
17 unary negation -
16 exponentiation **
15 multiplication *
15 division /
13 addition +
13 subtraction -
3 assignment =

As we can see, the "unary plus" has a priority of 17 which is higher than the 13 of "addition" (binary plus). That's why, in the expression "+apples + +oranges", unary pluses work before the addition.

Assignment

Let's note that an assignment = is also an operator. It is listed in the precedence table with the very low priority of 3.

That's why, when we assign a variable, like x = 2 * 2 + 1, the calculations are done first and then the = is evaluated, storing the result in x.

                                                        
                                                          
                                                            let x 
                                                            =
                                                            2
                                                            *
                                                            2
                                                            +
                                                            1
                                                            ;
                                                          
                                                          
                                                          
                                                            alert( x )
                                                            ;
                                                            // 5
                                                          
                                                        
                                                      

Assignment = returns a value

The fact of = being an operator, not a "magical" language construct has an interesting implication.

All operators in JavaScript return a value. That's obvious for + and -, but also true for =.

The call x = value writes the value into x and then returns it.

Here's a demo that uses an assignment as part of a more complex expression:

run let a = 1; let b = 2;

! let c = 3 - (a = b + 1); /!

alert( a ); // 3 alert( c ); // 0

In the example above, the result of expression (a = b + 1) is the value which was assigned to a (that is 3). It is then used for further evaluations.

Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries.

Although, please don't write the code like that. Such tricks definitely don't make code clearer or readable.

Chaining assignments

Another interesting feature is the ability to chain assignments:

run let a, b, c;

! a = b = c = 2 + 2; /!

alert( a ); // 4 alert( b ); // 4 alert( c ); // 4

Chained assignments evaluate from right to left. First, the rightmost expression 2 + 2 is evaluated and then assigned to the variables on the left: c, b and a. At the end, all the variables share a single value.

Once again, for the purposes of readability it's better to split such code into few lines:

                                                        
                                                          c 
                                                            =
                                                            2
                                                            +
                                                            2
                                                            ;
                                                          
                                                          b 
                                                            = c
                                                            ;
                                                          
                                                          a 
                                                            = c
                                                            ;
                                                          
                                                        
                                                      

That's easier to read, especially when eye-scanning the code fast.

Modify-in-place

We often need to apply an operator to a variable and store the new result in that same variable.

For example:

                                                        
                                                          
                                                            let n 
                                                            =
                                                            2
                                                            ;
                                                          
                                                          n 
                                                            = n 
                                                            +
                                                            5
                                                            ;
                                                          
                                                          n 
                                                            = n 
                                                            *
                                                            2
                                                            ;
                                                          
                                                        
                                                      

This notation can be shortened using the operators += and *=:

run let n = 2; n += 5; // now n = 7 (same as n = n + 5) n = 2; // now n = 14 (same as n = n 2)

alert( n ); // 14

Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: /=, -=, etc.

Such operators have the same precedence as a normal assignment, so they run after most other calculations:

run let n = 2;

n *= 3 + 5;

alert( n ); // 16 (right part evaluated first, same as n *= 8)

Increment/decrement

Increasing or decreasing a number by one is among the most common numerical operations.

So, there are special operators for it:

                                                      Increment/decrement can only be applied to variables. Trying to use it on a value like `5++` will give an error.
                                                    

The operators ++ and -- can be placed either before or after a variable.

Both of these statements do the same thing: increase counter by 1.

Is there any difference? Yes, but we can only see it if we use the returned value of ++/--.

Let's clarify. As we know, all operators return a value. Increment/decrement is no exception. The prefix form returns the new value while the postfix form returns the old value (prior to increment/decrement).

To see the difference, here's an example:

run let counter = 1; let a = ++counter; // (*)

alert(a); // !2 /!

In the line (*), the prefix form ++counter increments counter and returns the new value, 2. So, the alert shows 2.

Now, let's use the postfix form:

run let counter = 1; let a = counter++; // (*) changed ++counter to counter++

alert(a); // !1 /!

In the line (*), the postfix form counter++ also increments counter but returns the old value (prior to increment). So, the alert shows 1.

To summarize:

smart header="Increment/decrement among other operators" The operators++/-` can be used inside expressions as well. Their precedence is higher than most other arithmetical operations.

For instance:

js run let counter = 1; alert( 2 * ++counter ); // 4

Compare with:

js run let counter = 1; alert( 2 * counter++ ); // 2, because counter++ returns the "old" value

Though technically okay, such notation usually makes code less readable. One line does multiple things - not good.

While reading code, a fast "vertical" eye-scan can easily miss something like counter++ and it won't be obvious that the variable increased.

We advise a style of "one line - one action":

js run let counter = 1; alert( 2 * counter ); counter++;

Bitwise operators

Bitwise operators treat arguments as 32-bit integer numbers and work on the level of their binary representation.

These operators are not JavaScript-specific. They are supported in most programming languages.

The list of operators:

These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the Bitwise Operators chapter on MDN when a need arises.

Comma

The comma operator , is one of the rarest and most unusual operators. Sometimes, it's used to write shorter code, so we need to know it in order to understand what's going on.

The comma operator allows us to evaluate several expressions, dividing them with a comma ,. Each of them is evaluated but only the result of the last one is returned.

For example:

run ! let a = (1 + 2, 3 + 4); /!

alert( a ); // 7 (the result of 3 + 4)

Here, the first expression 1 + 2 is evaluated and its result is thrown away. Then, 3 + 4 is evaluated and returned as the result.

`` smart header="Comma has a very low precedence" Please note that the comma operator has very low precedence, lower than=`, so parentheses are important in the example above.

Without them: a = 1 + 2, 3 + 4 evaluates + first, summing the numbers into a = 3, 7, then the assignment operator = assigns a = 3, and the rest is ignored. It's like (a = 1 + 2), 3 + 4.

Why do we need an operator that throws away everything except the last expression?

Sometimes, people use it in more complex constructs to put several actions in one line.

For example:

                                                        
                                                          
                                                            // three operations in one line
                                                          
                                                          
                                                            for (
                                                            *!*a 
                                                            =
                                                            1
                                                            , b 
                                                            =
                                                            3
                                                            , c 
                                                            = a 
                                                            * b
                                                            *
                                                            /!
                                                            *
                                                            ; a < 10; a
                                                            ++)
                                                             {
                                                          
                                                          
                                                             ...
                                                          
                                                          
                                                            }
                                                          
                                                        
                                                      

Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But usually they don't improve code readability so we should think well before using them.

Scripts: async, defer

In modern websites, scripts are often "heavier" than HTML: their download size is larger, and processing time is also longer.

When the browser loads HTML and comes across a <script>...</script> tag, it can't continue building the DOM. It must execute the script right now. The same happens for external scripts <script src="..."></script>: the browser must wait for the script to download, execute the downloaded script, and only then can it process the rest of the page.

That leads to two important issues:

  1. Scripts can't see DOM elements below them, so they can't add handlers etc.
  2. If there's a bulky script at the top of the page, it "blocks the page". Users can't see the page content till it downloads and runs:
run height=100

…content before script…

…content after script…

There are some workarounds to that. For instance, we can put a script at the bottom of the page. Then it can see elements above it, and it doesn't block the page content from showing:

                                                        
                                                          
                                                            <body>
                                                          
                                                            ...all content is above the script...
                                                          
                                                          
                                                            <script
                                                             src=
                                                            "https://javascript.info/article/script-async-defer/long.js?speed=1"
                                                            ></script>
                                                          
                                                          
                                                            </body>
                                                          
                                                        
                                                      

But this solution is far from perfect. For example, the browser notices the script (and can start downloading it) only after it downloaded the full HTML document. For long HTML documents, that may be a noticeable delay.

Such things are invisible for people using very fast connections, but many people in the world still have slow internet speeds and use a far-from-perfect mobile internet connection.

Luckily, there are two <script> attributes that solve the problem for us: defer and async.

defer

The defer attribute tells the browser not to wait for the script. Instead, the browser will continue to process the HTML, build DOM. The script loads "in the background", and then runs when the DOM is fully built.

Here's the same example as above, but with defer:

run height=100

…content before script…

…content after script…

In other words:

The following example demonstrates the second part:

run height=100

…content before scripts…

…content after scripts…

  1. The page content shows up immediately.
  2. DOMContentLoaded event handler waits for the deferred script. It only triggers when the script is downloaded and executed.

Deferred scripts keep their relative order, just like regular scripts.

Let's say, we have two deferred scripts: the long.js and then small.js:

                                                        
                                                          
                                                            <script
                                                             defer src=
                                                            "https://javascript.info/article/script-async-defer/long.js"
                                                            ></script>
                                                          
                                                          
                                                            <script
                                                             defer src=
                                                            "https://javascript.info/article/script-async-defer/small.js"
                                                            ></script>
                                                          
                                                        
                                                      

Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The small.js probably finishes first.

…But the defer attribute, besides telling the browser "not to block", ensures that the relative order is kept. So even though small.js loads first, it still waits and runs after long.js executes.

That may be important for cases when we need to load a JavaScript library and then a script that depends on it.

smart header="The `defer` attribute is only for external scripts" The `defer` attribute is ignored if the `<script>` tag has no `src`.

async

The async attribute is somewhat like defer. It also makes the script non-blocking. But it has important differences in the behavior.

The async attribute means that a script is completely independent:

In other words, async scripts load in the background and run when ready. The DOM and other scripts don't wait for them, and they don't wait for anything. A fully independent script that runs when loaded. As simple, as it can get, right?

Here's an example similar to what we've seen with defer: two scripts long.js and small.js, but now with async instead of defer.

They don't wait for each other. Whatever loads first (probably small.js) - runs first:

run height=100

…content before scripts…

…content after scripts…

Async scripts are great when we integrate an independent third-party script into the page: counters, ads and so on, as they don't depend on our scripts, and our scripts shouldn't wait for them:

                                                        
                                                          
                                                            <!-- Google Analytics is usually added like this -->
                                                          
                                                          
                                                            <script
                                                             async src=
                                                            "https://google-analytics.com/analytics.js"
                                                            ></script>
                                                          
                                                        
                                                      

Dynamic scripts

There's one more important way of adding a script to the page.

We can create a script and append it to the document dynamically using JavaScript:

js run let script = document.createElement('script'); script.src = "/article/script-async-defer/long.js"; document.body.append(script); // (*)

The script starts loading as soon as it's appended to the document (*).

Dynamic scripts behave as "async" by default.

That is: - They don't wait for anything, nothing waits for them. - The script that loads first - runs first ("load-first" order).

This can be changed if we explicitly set script.async=false. Then scripts will be executed in the document order, just like defer.

In this example, loadScript(src) function adds a script and also sets async to false.

So long.js always runs first (as it's added first):

run function loadScript(src) { let script = document.createElement(‘script'); script.src = src; script.async = false; document.body.append(script); }

// long.js runs first because of async=false loadScript("/article/script-async-defer/long.js"); loadScript("/article/script-async-defer/small.js");

Without script.async=false, scripts would execute in default, load-first order (the small.js probably first).

Again, as with the defer, the order matters if we'd like to load a library and then another script that depends on it.

Summary

Both async and defer have one common thing: downloading of such scripts doesn't block page rendering. So the user can read page content and get acquainted with the page immediately.

But there are also essential differences between them:

Order DOMContentLoaded
async Load-first order. Their document order doesn't matter - which loads first runs first Irrelevant. May load and execute while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough.
defer Document order (as they go in the document). Execute after the document is loaded and parsed (they wait if needed), right before DOMContentLoaded.

In practice, defer is used for scripts that need the whole DOM and/or their relative execution order is important.

And async is used for independent scripts, like counters or ads. And their relative execution order does not matter.

`` warn header="Page without scripts should be usable" Please note: if you're usingdefer orasync`, then user will see the the page before the script loads.

In such case, some graphical components are probably not initialized yet.

Don't forget to put "loading" indication and disable buttons that aren't functional yet. Let the user clearly see what he can do on the page, and what's still getting ready.

Resource loading: onload and onerror

The browser allows us to track the loading of external resources - scripts, iframes, pictures and so on.

There are two events for it:

Loading a script

Let's say we need to load a third-party script and call a function that resides there.

We can load it dynamically, like this:

                                                        
                                                          
                                                            let script 
                                                            =
                                                            document.
                                                            createElement(
                                                            'script')
                                                            ;
                                                          
                                                          
                                                            script.
                                                            src
                                                            =
                                                            "my.js"
                                                            ;
                                                          
                                                          
                                                          
                                                            document.
                                                            head.
                                                            append(script)
                                                            ;
                                                          
                                                        
                                                      

…But how to run the function that is declared inside that script? We need to wait until the script loads, and only then we can call it.

                                                      For our own scripts we could use [JavaScript modules](info:modules) here, but they are not widely adopted by third-party libraries.
                                                    

script.onload

The main helper is the load event. It triggers after the script was loaded and executed.

For instance:

run untrusted let script = document.createElement(‘script');

// can load any script, from any domain script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js" document.head.append(script);

! script.onload = function() { // the script creates a variable " " alert( .VERSION ); // shows library version }; /!

So in onload we can use script variables, run functions etc.

…And what if the loading failed? For instance, there's no such script (error 404) or the server is down (unavailable).

script.onerror

Errors that occur during the loading of the script can be tracked in an error event.

For instance, let's request a script that doesn't exist:

run let script = document.createElement(‘script'); script.src = "https://example.com/404.js"; // no such script document.head.append(script);

! script.onerror = function() { alert("Error loading" + this.src); // Error loading https://example.com/404.js }; /!

Please note that we can't get HTTP error details here. We don't know if it was an error 404 or 500 or something else. Just that the loading failed.

                                                      Events `onload`/`onerror` track only the loading itself.
Errors that may occur during script processing and execution are out of scope for these events. That is: if a script loaded successfully, then `onload` triggers, even if it has programming errors in it. To track script errors, one can use `window.onerror` global handler.
                                                    

Other resources

The load and error events also work for other resources, basically for any resource that has an external src.

For example:

run let img = document.createElement(‘img'); img.src = "https://js.cx/clipart/train.gif"; // (*)

img.onload = function() { alert( Image loaded, size ${img.width}x${img.height}); };

img.onerror = function() { alert("Error occurred while loading image"); };

There are some notes though:

That's for historical reasons.

Crossorigin policy

There's a rule: scripts from one site can't access contents of the other site. So, e.g. a script at https://facebook.com can't read the user's mailbox at https://gmail.com.

Or, to be more precise, one origin (domain/port/protocol triplet) can't access the content from another one. So even if we have a subdomain, or just another port, these are different origins with no access to each other.

This rule also affects resources from other domains.

If we're using a script from another domain, and there's an error in it, we can't get error details.

For example, let's take a script error.js that consists of a single (bad) function call:

                                                        
                                                          
                                                            // 📁 error.js
                                                          
                                                          
                                                            noSuchFunction()
                                                            ;
                                                          
                                                        
                                                      

Now load it from the same site where it's located:

html run height=0 <script> window.onerror = function(message, url, line, col, errorObj) { alert(`${message}\n${url}, ${line}:${col}`); }; </script> <script src="/article/onload-onerror/crossorigin/error.js"></script>

We can see a good error report, like this:

                                                      Uncaught ReferenceError: noSuchFunction is not defined
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
                                                    

Now let's load the same script from another domain:

html run height=0 <script> window.onerror = function(message, url, line, col, errorObj) { alert(`${message}\n${url}, ${line}:${col}`); }; </script> <script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>

The report is different, like this:

                                                      Script error.
, 0:0
                                                    

Details may vary depending on the browser, but the idea is the same: any information about the internals of a script, including error stack traces, is hidden. Exactly because it's from another domain.

Why do we need error details?

There are many services (and we can build our own) that listen for global errors using window.onerror, save errors and provide an interface to access and analyze them. That's great, as we can see real errors, triggered by our users. But if a script comes from another origin, then there's not much information about errors in it, as we've just seen.

Similar cross-origin policy (CORS) is enforced for other types of resources as well.

To allow cross-origin access, the <script> tag needs to have the crossorigin attribute, plus the remote server must provide special headers.

There are three levels of cross-origin access:

  1. No crossorigin attribute - access prohibited.
  2. crossorigin="anonymous" - access allowed if the server responds with the header Access-Control-Allow-Origin with * or our origin. Browser does not send authorization information and cookies to remote server.
  3. crossorigin="use-credentials" - access allowed if the server sends back the header Access-Control-Allow-Origin with our origin and Access-Control-Allow-Credentials: true. Browser sends authorization information and cookies to remote server.
                                                      You can read more about cross-origin access in the chapter <info:fetch-crossorigin>. It describes the `fetch` method for network requests, but the policy is exactly the same.
Such thing as "cookies" is out of our current scope, but you can read about them in the chapter <info:cookie>.
                                                    

In our case, we didn't have any crossorigin attribute. So the cross-origin access was prohibited. Let's add it.

We can choose between "anonymous" (no cookies sent, one server-side header needed) and "use-credentials" (sends cookies too, two server-side headers needed).

If we don't care about cookies, then "anonymous" is the way to go:

html run height=0 <script> window.onerror = function(message, url, line, col, errorObj) { alert(`${message}\n${url}, ${line}:${col}`); }; </script> <script *!*crossorigin="anonymous"*/!* src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>

Now, assuming that the server provides an Access-Control-Allow-Origin header, everything's fine. We have the full error report.

Summary

Images <img>, external styles, scripts and other resources provide load and error events to track their loading:

The only exception is <iframe>: for historical reasons it always triggers load, for any load completion, even if the page is not found.

The readystatechange event also works for resources, but is rarely used, because load/error events are simpler.

Mutation observer

MutationObserver is a built-in object that observes a DOM element and fires a callback when it detects a change.

We'll first take a look at the syntax, and then explore a real-world use case, to see where such thing may be useful.

Syntax

MutationObserver is easy to use.

First, we create an observer with a callback-function:

                                                        
                                                          
                                                            let observer 
                                                            =
                                                            new
                                                            MutationObserver(callback)
                                                            ;
                                                          
                                                        
                                                      

And then attach it to a DOM node:

                                                        
                                                          
                                                            observer.
                                                            observe(node
                                                            , config)
                                                            ;
                                                          
                                                        
                                                      

config is an object with boolean options "what kind of changes to react on": - childList - changes in the direct children of node, - subtree - in all descendants of node, - attributes - attributes of node, - attributeFilter - an array of attribute names, to observe only selected ones. - characterData - whether to observe node.data (text content),

Few other options: - attributeOldValue - if true, pass both the old and the new value of attribute to callback (see below), otherwise only the new one (needs attributes option), - characterDataOldValue - if true, pass both the old and the new value of node.data to callback (see below), otherwise only the new one (needs characterData option).

Then after any changes, the callback is executed: changes are passed in the first argument as a list of MutationRecord objects, and the observer itself as the second argument.

MutationRecord objects have properties:

For example, here's a <div> with a contentEditable attribute. That attribute allows us to focus on it and edit.

run
Click and edit, please

If we run this code in the browser, then focus on the given <div> and change the text inside <b>edit</b>, console.log will show one mutation:

                                                        
                                                          mutationRecords 
                                                            = [
                                                            {
                                                          
                                                          
                                                            type
                                                            :
                                                            "characterData"
                                                            ,
                                                          
                                                          
                                                            oldValue
                                                            :
                                                            "edit"
                                                            ,
                                                          
                                                          
                                                            target
                                                            :
                                                            <text node
                                                            >,
                                                          
                                                          
                                                            // other properties empty
                                                          
                                                          
                                                            }]
                                                            ;
                                                          
                                                        
                                                      

If we make more complex editing operations, e.g. remove the <b>edit</b>, the mutation event may contain multiple mutation records:

                                                        
                                                          mutationRecords 
                                                            = [
                                                            {
                                                          
                                                          
                                                            type
                                                            :
                                                            "childList"
                                                            ,
                                                          
                                                          
                                                            target
                                                            :
                                                            <div#elem
                                                            >,
                                                          
                                                          
                                                            removedNodes
                                                            : [
                                                            <b
                                                            >]
                                                            ,
                                                          
                                                          
                                                            nextSibling
                                                            :
                                                            <text node
                                                            >,
                                                          
                                                          
                                                            previousSibling
                                                            :
                                                            <text node
                                                            >
                                                          
                                                          
                                                            // other properties empty
                                                          
                                                          
                                                            },
                                                            {
                                                          
                                                          
                                                            type
                                                            :
                                                            "characterData"
                                                          
                                                          
                                                            target
                                                            :
                                                            <text node
                                                            >
                                                          
                                                          
                                                            // ...mutation details depend on how the browser handles such removal
                                                          
                                                          
                                                            // it may coalesce two adjacent text nodes "edit " and ", please" into one node
                                                          
                                                          
                                                            // or it may leave them separate text nodes
                                                          
                                                          
                                                            }]
                                                            ;
                                                          
                                                        
                                                      

So, MutationObserver allows to react on any changes within DOM subtree.

Usage for integration

When such thing may be useful?

Imagine the situation when you need to add a third-party script that contains useful functionality, but also does something unwanted, e.g. shows ads <div class="ads">Unwanted ads</div>.

Naturally, the third-party script provides no mechanisms to remove it.

Using MutationObserver, we can detect when the unwanted element appears in our DOM and remove it.

There are other situations when a third-party script adds something into our document, and we'd like to detect, when it happens, to adapt our page, dynamically resize something etc.

MutationObserver allows to implement this.

Usage for architecture

There are also situations when MutationObserver is good from architectural standpoint.

Let's say we're making a website about programming. Naturally, articles and other materials may contain source code snippets.

Such snippet in an HTML markup looks like this:

                                                        
                                                          ...
                                                          
                                                            <pre
                                                             class=
                                                            "language-javascript"
                                                            ><code>
                                                          
                                                            // here's the code
                                                            let hello = "world";
                                                          
                                                            </code></pre>
                                                          
                                                          ...
                                                        
                                                      

For better readability and at the same time, to beautify it, we'll be using a JavaScript syntax highlighting library on our site, like Prism.js. To get syntax highlighting for above snippet in Prism, Prism.highlightElem(pre) is called, which examines the contents of such pre elements and adds special tags and styles for colored syntax highlighting into those elements, similar to what you see in examples here, on this page.

When exactly should we run that highlighting method? Well, we can do it on DOMContentLoaded event, or put the script at the bottom of the page. The moment our DOM is ready, we can search for elements pre[class*="language"] and call Prism.highlightElem on them:

                                                        
                                                          
                                                            // highlight all code snippets on the page
                                                          
                                                          
                                                            document.
                                                            querySelectorAll(
                                                            'pre[class*="language"]').
                                                            forEach(
                                                            Prism.
                                                            highlightElem)
                                                            ;
                                                          
                                                        
                                                      

Everything's simple so far, right? We find code snippets in HTML and highlight them.

Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that later in the tutorial. For now it only matters that we fetch an HTML article from a webserver and display it on demand:

                                                        
                                                          
                                                            let article 
                                                            =
                                                            /* fetch new content from server */
                                                          
                                                          
                                                            articleElem.
                                                            innerHTML
                                                            = article
                                                            ;
                                                          
                                                        
                                                      

The new article HTML may contain code snippets. We need to call Prism.highlightElem on them, otherwise they won't get highlighted.

Where and when to call Prism.highlightElem for a dynamically loaded article?

We could append that call to the code that loads an article, like this:

                                                        
                                                          
                                                            let article 
                                                            =
                                                            /* fetch new content from server */
                                                          
                                                          
                                                            articleElem.
                                                            innerHTML
                                                            = article
                                                            ;
                                                          
                                                          
                                                          
                                                            *!*
                                                          
                                                          
                                                            let snippets 
                                                            =
                                                            articleElem.
                                                            querySelectorAll(
                                                            'pre[class*="language-"]')
                                                            ;
                                                          
                                                          
                                                            snippets.
                                                            forEach(
                                                            Prism.
                                                            highlightElem)
                                                            ;
                                                          
                                                          
                                                            *
                                                            /!
                                                            *
                                                          
                                                        
                                                      

…But, imagine if we have many places in the code where we load our content - articles, quizzes, forum posts, etc. Do we need to put the highlighting call everywhere, to highlight the code in content after loading? That's not very convenient.

And what if the content is loaded by a third-party module? For example, we have a forum written by someone else, that loads content dynamically, and we'd like to add syntax highlighting to it. No one likes patching third-party scripts.

Luckily, there's another option.

We can use MutationObserver to automatically detect when code snippets are inserted into the page and highlight them.

So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.

Dynamic highlight demo

Here's the working example.

If you run this code, it starts observing the element below and highlighting any code snippets that appear there:

run let observer = new MutationObserver(mutations => {

for(let mutation of mutations) { // examine new nodes, is there anything to highlight?

                                                      for(let node of mutation.addedNodes) {
// we track only elements, skip other nodes (e.g. text nodes)
if (!(node instanceof HTMLElement)) continue;
// check the inserted element for being a code snippet
if (node.matches('pre[class*="language-"]')) {
Prism.highlightElement(node);
}
// or maybe there's a code snippet somewhere in its subtree?
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
Prism.highlightElement(elem);
}
}
                                                    

}

});

let demoElem = document.getElementById(‘highlight-demo');

observer.observe(demoElem, {childList: true, subtree: true});

Here, below, there's an HTML-element and JavaScript that dynamically fills it using innerHTML.

Please run the previous code (above, observes that element), and then the code below. You'll see how MutationObserver detects and highlights the snippet.

A demo-element with id="highlight-demo", run the code above to observe it.

The following code populates its innerHTML, that causes the MutationObserver to react and highlight its contents:

run let demoElem = document.getElementById(‘highlight-demo');

// dynamically insert content with code snippets demoElem.innerHTML = A code snippet is below: <pre class="language-javascript"><code> let hello = "world!"; </code></pre> <div>Another one:</div> <div> <pre class="language-css"><code>.class { margin: 5px; } </code></pre> </div>;

Now we have MutationObserver that can track all highlighting in observed elements or the whole document. We can add/remove code snippets in HTML without thinking about it.

Additional methods

There's a method to stop observing the node:

When we stop the observing, it might be possible that some changes were not yet processed by the observer. In such cases, we use

These methods can be used together, like this:

                                                        
                                                          
                                                            // get a list of unprocessed mutations
                                                          
                                                          
                                                            // should be called before disconnecting,
                                                          
                                                          
                                                            // if you care about possibly unhandled recent mutations
                                                          
                                                          
                                                            let mutationRecords 
                                                            =
                                                            observer.
                                                            takeRecords()
                                                            ;
                                                          
                                                          
                                                          
                                                            // stop tracking changes
                                                          
                                                          
                                                            observer.
                                                            disconnect()
                                                            ;
                                                          
                                                          ...
                                                        
                                                      

smart header="Records returned by `observer.takeRecords()` are removed from the processing queue" The callback won't be called for records, returned by `observer.takeRecords()`.

smart header="Garbage collection interaction" Observers use weak references to nodes internally. That is, if a node is removed from the DOM, and becomes unreachable, then it can be garbage collected.

The mere fact that a DOM node is observed doesn't prevent the garbage collection.

Summary

MutationObserver can react to changes in DOM - attributes, text content and adding/removing elements.

We can use it to track changes introduced by other parts of our code, as well as to integrate with third-party scripts.

MutationObserver can track any changes. The config "what to observe" options are used for optimizations, not to spend resources on unneeded callback invocations.

libs: - d3 - domtree


Selection and Range

In this chapter we'll cover selection in the document, as well as selection in form fields, such as <input>.

JavaScript can access an existing selection, select/deselect DOM nodes as a whole or partially, remove the selected content from the document, wrap it into a tag, and so on.

You can find some recipes for common tasks at the end of the chapter, in "Summary" section. Maybe that covers your current needs, but you'll get much more if you read the whole text.

The underlying Range and Selection objects are easy to grasp, and then you'll need no recipes to make them do what you want.

Range

The basic concept of selection is Range, that is essentially a pair of "boundary points": range start and range end.

A Range object is created without parameters:

                                                        
                                                          
                                                            let range 
                                                            =
                                                            new
                                                            Range()
                                                            ;
                                                          
                                                        
                                                      

Then we can set the selection boundaries using range.setStart(node, offset) and range.setEnd(node, offset).

As you might guess, further we'll use the Range objects for selection, but first let's create few such objects.

Selecting the text partially

The interesting thing is that the first argument node in both methods can be either a text node or an element node, and the meaning of the second argument depends on that.

If node is a text node, then offset must be the position in its text.

For example, given the element <p>Hello</p>, we can create the range containing the letters "ll" as follows:

run

Hello

Here we take the first child of <p> (that's the text node) and specify the text positions inside it:

Selecting element nodes

Alternatively, if node is an element node, then offset must be the child number.

That's handy for making ranges that contain nodes as a whole, not stop somewhere inside their text.

For example, we have a more complex document fragment:

html autorun <p id="p">Example: <i>italic</i> and <b>bold</b></p>

Here's its DOM structure with both element and text nodes:

Let's make a range for "Example: <i>italic</i>".

As we can see, this phrase consists of exactly two children of <p>, with indexes 0 and 1:

Here's the demo. If you run it, you can see that the text gets selected:

run

Example: italic and bold

Here's a more flexible test stand where you can set range start/end numbers and explore other variants:

run autorun

Example: italic and bold

From - To

E.g. selecting in the same <p> from offset 1 to 4 gives us the range <i>italic</i> and <b>bold</b>:

smart header="Starting and ending nodes can be different" We don't have to use the same node in `setStart` and `setEnd`. A range may span across many unrelated nodes. It's only important that the end is after the start in the document.

Selecting a bigger fragment

Let's make a bigger selection in our example, like this:

We already know how to do that. We just need to set the start and the end as a relative offset in text nodes.

We need to create a range, that: - starts from position 2 in <p> first child (taking all but two first letters of "Ex ample:") - ends at the position 3 in <b> first child (taking first three letters of " bold", but no more):

run

Example: italic and bold

As you can see, it's fairly easy to make a range of whatever we want.

If we'd like to take nodes as a whole, we can pass elements in setStart/setEnd. Otherwise, we can work on the text level.

Range properties

The range object that we created in the example above has following properties:

Range selection methods

There are many convenience methods to manipulate ranges.

We've already seen setStart and setEnd, here are other similar methods.

Set range start:

Set range end (similar methods):

Technically, setStart/setEnd can do anything, but more methods provide more convenience.

In all these methods, node can be both a text or element node: for text nodes offset skips that many of characters, while for element nodes that many child nodes.

Even more methods to create ranges: - selectNode(node) set range to select the whole node - selectNodeContents(node) set range to select the whole node contents - collapse(toStart) if toStart=true set end=start, otherwise set start=end, thus collapsing the range - cloneRange() creates a new range with the same start/end

Range editing methods

Once the range is created, we can manipulate its content using these methods:

With these methods we can do basically anything with selected nodes.

Here's the test stand to see them in action:

run refresh autorun height=260 Click buttons to run methods on the selection, "resetExample" to reset it.

Example: italic and bold

There also exist methods to compare ranges, but these are rarely used. When you need them, please refer to the spec or MDN manual.

Selection

Range is a generic object for managing selection ranges. Although, creating a Range doesn't mean that we see a selection on screen.

We may create Range objects, pass them around - they do not visually select anything on their own.

The document selection is represented by Selection object, that can be obtained as window.getSelection() or document.getSelection(). A selection may include zero or more ranges. At least, the Selection API specification says so. In practice though, only Firefox allows to select multiple ranges in the document by using key:Ctrl+click ( key:Cmd+click for Mac).

Here's a screenshot of a selection with 3 ranges, made in Firefox:

Other browsers support at maximum 1 range. As we'll see, some of Selection methods imply that there may be many ranges, but again, in all browsers except Firefox, there's at maximum 1.

Here's a small demo that shows the current selection (select something and click) as text:

Selection properties

As said, a selection may in theory contain multiple ranges. We can get these range objects using the method:

Also, there exist properties that often provide better convenience.

Similar to a range, a selection object has a start, called "anchor", and the end, called "focus".

The main selection properties are:

smart header="Selection end/start vs Range"

There's an important differences of a selection anchor/focus compared with a Range start/end.

As we know, Range objects always have their start before the end.

For selections, that's not always the case.

Selecting something with a mouse can be done in both directions: either "left-to-right" or "right-to-left".

In other words, when the mouse button is pressed, and then it moves forward in the document, then its end (focus) will be after its start (anchor).

E.g. if the user starts selecting with mouse and goes from "Example" to "italic":

…But the same selection could be done backwards: starting from "italic" to "Example" (backward direction), then its end (focus) will be before the start (anchor):

Selection events

There are events on to keep track of selection:

Selection tracking demo

Here's a small demo. It tracks the current selection on the document and shows its boundaries:

run height=80

Select me: italic and bold

From - To

Selection copying demo

There are two approaches to copying the selected content:

  1. We can use document.getSelection().toString() to get it as text.
  2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with getRangesAt(...). A Range object, in turn, has cloneContents() method that clones its content and returns as DocumentFragment object, that we can insert elsewhere.

Here's the demo of copying the selected content both as text and as DOM nodes:

run height=100

Select me: italic and bold

Cloned:
As text:

Selection methods

We can work with the selection by addding/removing ranges:

There are also convenience methods to manipulate the selection range directly, without intermediate Range calls:

For most tasks these methods are just fine, there's no need to access the underlying Range object.

For example, selecting the whole contents of the paragraph <p>:

run

Select me: italic and bold

The same thing using ranges:

run

Select me: italic and bold

`` smart header="To select something, remove the existing selection first" If a document selection already exists, empty it first withremoveAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges.

The exception is some selection methods, that replace the existing selection, such as setBaseAndExtent.

Selection in form controls

Form elements, such as input and textarea provide special API for selection, without Selection or Range objects. As an input value is a pure text, not HTML, there's no need for such objects, everything's much simpler.

Properties: - input.selectionStart - position of selection start (writeable), - input.selectionEnd - position of selection end (writeable), - input.selectionDirection - selection direction, one of: "forward", "backward" or "none" (if e.g. selected with a double mouse click),

Events: - input.onselect - triggers when something is selected.

Methods:

Now let's see these methods in action.

Example: tracking selection

For example, this code uses onselect event to track selection:

run autorun


From - To

Please note: - onselect triggers when something is selected, but not when the selection is removed. - document.onselectionchange event should not trigger for selections inside a form control, according to the spec, as it's not related to document selection and ranges. Some browsers generate it, but we shouldn't rely on it.

Example: moving cursor

We can change selectionStart and selectionEnd, that sets the selection.

An important edge case is when selectionStart and selectionEnd equal each other. Then it's exactly the cursor position. Or, to rephrase, when nothing is selected, the selection is collapsed at the cursor position.

So, by setting selectionStart and selectionEnd to the same value, we move the cursor.

For example:

run autorun

Example: modifying selection

To modify the content of the selection, we can use input.setRangeText() method. Of course, we can read selectionStart/End and, with the knowledge of the selection, change the corresponding substring of value, but setRangeText is more powerful and often more convenient.

That's a somewhat complex method. In its simplest one-argument form it replaces the user selected range and removes the selection.

For example, here the user selection will be wrapped by *...*:

run autorun

With more arguments, we can set range start and end.

In this example we find "THIS" in the input text, replace it and keep the replacement selected:

run autorun

Example: insert at cursor

If nothing is selected, or we use equal start and end in setRangeText, then the new text is just inserted, nothing is removed.

We can also insert something "at the cursor" using setRangeText.

Here's a button that inserts "HELLO" at the cursor position and puts the cursor immediately after it. If the selection is not empty, then it gets replaced (we can detect it by comparing selectionStart!=selectionEnd and do something else instead):

run autorun

Making unselectable

To make something unselectable, there are three ways:

  1. Use CSS property user-select: none.

    html run <style> #elem { user-select: none; } </style> <div>Selectable <div id="elem">Unselectable</div> Selectable</div>

    This doesn't allow the selection to start at elem. But the user may start the selection elsewhere and include elem into it.

    Then elem will become a part of document.getSelection(), so the selection actually happens, but its content is usually ignored in copy-paste.

  2. Prevent default action in onselectstart or mousedown events.

    run
    Selectable
    Unselectable
    Selectable

    This prevents starting the selection on elem, but the visitor may start it at another element, then extend to elem.

    That's convenient when there's another event handler on the same action that triggers the select (e.g. mousedown). So we disable the selection to avoid conflict, still allowing elem contents to be copied.

  3. We can also clear the selection post-factum after it happens with document.getSelection().empty(). That's rarely used, as this causes unwanted blinking as the selection appears-disappears.

References

Summary

We covered two different APIs for selections:

  1. For document: Selection and Range objects.
  2. For input, textarea: additional methods and properties.

The second API is very simple, as it works with text.

The most used recipes are probably:

  1. Getting the selection: let selection = document.getSelection();

    let cloned = /* element to clone the selected nodes to */;

    // then apply Range methods to selection.getRangeAt(0) // or, like here, to all ranges to support multi-select for (let i = 0; i < selection.rangeCount; i++) { cloned.append(selection.getRangeAt(i).cloneContents()); }
  2. Setting the selection: let selection = document.getSelection();

    // directly: selection.setBaseAndExtent(…from…to…);

    // or we can create a range and: selection.removeAllRanges(); selection.addRange(range);

And finally, about the cursor. The cursor position in editable elements, like <textarea> is always at the start or the end of the selection. We can use it to get cursor position or to move the cursor by setting elem.selectionStart and elem.selectionEnd.

Event loop: microtasks and macrotasks

Browser JavaScript execution flow, as well as in Node.js, is based on an event loop.

Understanding how event loop works is important for optimizations, and sometimes for the right architecture.

In this chapter we first cover theoretical details about how things work, and then see practical applications of that knowledge.

Event Loop

The event loop concept is very simple. There's an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks.

The general algorithm of the engine:

  1. While there are tasks:
    • execute them, starting with the oldest task.
  2. Sleep until a task appears, then go to 1.

That's a formalization for what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates.

Examples of tasks:

Tasks are set - the engine handles them - then waits for more tasks (while sleeping and consuming close to zero CPU).

It may happen that a task comes while the engine is busy, then it's enqueued.

The tasks form a queue, so-called "macrotask queue" (v8 term):

For instance, while the engine is busy executing a script, a user may move their mouse causing mousemove, and setTimeout may be due and so on, these tasks form a queue, as illustrated on the picture above.

Tasks from the queue are processed on "first come - first served" basis. When the engine browser is done with the script, it handles mousemove event, then setTimeout handler, and so on.

So far, quite simple, right?

Two more details: 1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. 2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after a time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop.

That was the theory. Now let's see how we can apply that knowledge.

Use-case 1: splitting CPU-hungry tasks

Let's say we have a CPU-hungry task.

For example, syntax-highlighting (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document - for a large amount of text that takes a lot of time.

While the engine is busy with syntax highlighting, it can't do other DOM-related stuff, process user events, etc. It may even cause the browser to "hiccup" or even "hang" for a bit, which is unacceptable.

We can avoid problems by splitting the big task into pieces. Highlight first 100 lines, then schedule setTimeout (with zero-delay) for the next 100 lines, and so on.

To demonstrate this approach, for the sake of simplicity, instead of text-highlighting, let's take a function that counts from 1 to 1000000000.

If you run the code below, the engine will "hang" for some time. For server-side JS that's clearly noticeable, and if you are running it in-browser, then try to click other buttons on the page - you'll see that no other events get handled until the counting finishes.

run let i = 0;

let start = Date.now();

function count() {

// do a heavy job for (let j = 0; j < 1e9; j++) { i++; }

alert("Done in" + (Date.now() - start) + ‘ms'); }

count();

The browser may even show a "the script takes too long" warning.

Let's split the job using nested setTimeout calls:

run let i = 0;

let start = Date.now();

function count() {

// do a piece of the heavy job (*) do { i++; } while (i % 1e6 != 0);

if (i == 1e9) { alert("Done in" + (Date.now() - start) + ‘ms'); } else { setTimeout(count); // schedule the new call (**) }

}

count();

Now the browser interface is fully functional during the "counting" process.

A single run of count does a part of the job (*), and then re-schedules itself (**) if needed:

  1. First run counts: i=1...1000000.
  2. Second run counts: i=1000001..2000000.
  3. …and so on.

Now, if a new side task (e.g. onclick event) appears while the engine is busy executing part 1, it gets queued and then executes when part 1 finished, before the next part. Periodic returns to the event loop between count executions provide just enough "air" for the JavaScript engine to do something else, to react to other user actions.

The notable thing is that both variants - with and without splitting the job by setTimeout - are comparable in speed. There's not much difference in the overall counting time.

To make them closer, let's make an improvement.

We'll move the scheduling to the beginning of the count():

run let i = 0;

let start = Date.now();

function count() {

// move the scheduling to the beginning if (i < 1e9 - 1e6) { setTimeout(count); // schedule the new call }

do { i++; } while (i % 1e6 != 0);

if (i == 1e9) { alert("Done in" + (Date.now() - start) + ‘ms'); }

}

count();

Now when we start to count() and see that we'll need to count() more, we schedule that immediately, before doing the job.

If you run it, it's easy to notice that it takes significantly less time.

Why?

That's simple: as you remember, there's the in-browser minimal delay of 4ms for many nested setTimeout calls. Even if we set 0, it's 4ms (or a bit more). So the earlier we schedule it - the faster it runs.

Finally, we've split a CPU-hungry task into parts - now it doesn't block the user interface. And its overall execution time isn't much longer.

Use case 2: progress indication

Another benefit of splitting heavy tasks for browser scripts is that we can show progress indication.

As mentioned earlier, changes to DOM are painted only after the currently running task is completed, irrespective of how long it takes.

On one hand, that's great, because our function may create many elements, add them one-by-one to the document and change their styles - the visitor won't see any "intermediate", unfinished state. An important thing, right?

Here's the demo, the changes to i won't show up until the function finishes, so we'll see only the last value:

run

…But we also may want to show something during the task, e.g. a progress bar.

If we split the heavy task into pieces using setTimeout, then changes are painted out in-between them.

This looks prettier:

run

Now the <div> shows increasing values of i, a kind of a progress bar.

Use case 3: doing something after the event

In an event handler we may decide to postpone some actions until the event bubbled up and was handled on all levels. We can do that by wrapping the code in zero delay setTimeout.

In the chapter info:dispatch-events we saw an example: custom event menu-open is dispatched in setTimeout, so that it happens after the "click" event is fully handled.

                                                                                  
                                                                                    
                                                                                      menu.
                                                                                      onclick
                                                                                      =
                                                                                      function() 
                                                                                      {
                                                                                    
                                                                                    
                                                                                      // ...
                                                                                    
                                                                                    
                                                                                    
                                                                                      // create a custom event with the clicked menu item data
                                                                                    
                                                                                    
                                                                                      let customEvent 
                                                                                      =
                                                                                      new
                                                                                      CustomEvent(
                                                                                      "menu-open"
                                                                                      ,
                                                                                      {
                                                                                    
                                                                                    
                                                                                      bubbles
                                                                                      :
                                                                                      true
                                                                                    
                                                                                    
                                                                                      })
                                                                                      ;
                                                                                    
                                                                                    
                                                                                    
                                                                                      // dispatch the custom event asynchronously
                                                                                    
                                                                                    
                                                                                      setTimeout(() 
                                                                                      =>
                                                                                      menu.
                                                                                      dispatchEvent(customEvent))
                                                                                      ;
                                                                                    
                                                                                    
                                                                                      };
                                                                                    
                                                                                  
                                                                                

Macrotasks and Microtasks

Along with macrotasks, described in this chapter, there are microtasks, mentioned in the chapter info:microtask-queue.

Microtasks come solely from our code. They are usually created by promises: an execution of .then/catch/finally handler becomes a microtask. Microtasks are used "under the cover" of await as well, as it's another form of promise handling.

There's also a special function queueMicrotask(func) that queues func for execution in the microtask queue.

Immediately after every macrotask, the engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else.

For instance, take a look:

run setTimeout(() => alert("timeout"));

Promise.resolve() .then(() => alert("promise"));

alert("code");

What's going to be the order here?

  1. code shows first, because it's a regular synchronous call.
  2. promise shows second, because .then passes through the microtask queue, and runs after the current code.
  3. timeout shows last, because it's a macrotask.

The richer event loop picture looks like this (order is from top to bottom, that is: the script first, then microtasks, rendering and so on):

All microtasks are completed before any other event handling or rendering or any other macrotask takes place.

That's important, as it guarantees that the application environment is basically the same (no mouse coordinate changes, no new network data, etc) between microtasks.

If we'd like to execute a function asynchronously (after the current code), but before changes are rendered or new events handled, we can schedule it with queueMicrotask.

Here's an example with "counting progress bar", similar to the one shown previously, but queueMicrotask is used instead of setTimeout. You can see that it renders at the very end. Just like the synchronous code:

run

Summary

A more detailed event loop algorithm (though still simplified compared to the specification):

  1. Dequeue and run the oldest task from the macrotask queue (e.g. "script").
  2. Execute all microtasks:
    • While the microtask queue is not empty:
      • Dequeue and run the oldest microtask.
  3. Render changes if any.
  4. If the macrotask queue is empty, wait till a macrotask appears.
  5. Go to step 1.

To schedule a new macrotask: - Use zero delayed setTimeout(f).

That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react to user events and show progress between them.

Also, used in event handlers to schedule an action after the event is fully handled (bubbling done).

To schedule a new microtask - Use queueMicrotask(f). - Also promise handlers go through the microtask queue.

There's no UI or network event handling between microtasks: they run immediately one after another.

So one may want to queueMicrotask to execute a function asynchronously, but within the environment state.

smart header="Web Workers" For long heavy calculations that shouldn't block the event loop, we can use Web Workers.

That's a way to run code in another, parallel thread.

Web Workers can exchange messages with the main process, but they have their own variables, and their own event loop.

Web Workers do not have access to DOM, so they are useful, mainly, for calculations, to use multiple CPU cores simultaneously.

Popups and window methods

A popup window is one of the oldest methods to show additional document to user.

Basically, you just run:

                                                                                  
                                                                                    
                                                                                      window.
                                                                                      open(
                                                                                      'https://javascript.info/')
                                                                                    
                                                                                  
                                                                                

…And it will open a new window with given URL. Most modern browsers are configured to open url in new tabs instead of separate windows.

Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: we can load content dynamically with fetch and show it in a dynamically generated <div>. So, popups is not something we use everyday.

Also, popups are tricky on mobile devices, that don't show multiple windows simultaneously.

Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/…), because:

  1. A popup is a separate window which has its own independent JavaScript environment. So opening a popup from a third-party, non-trusted site is safe.
  2. It's very easy to open a popup.
  3. A popup can navigate (change URL) and send messages to the opener window.

In the past, evil sites abused popups a lot. A bad page could open tons of popup windows with ads. So now most browsers try to block popups and protect the user.

Most browsers block popups if they are called outside of user-triggered event handlers like onclick.

For example:

                                                                                  
                                                                                    
                                                                                      // popup blocked
                                                                                    
                                                                                    
                                                                                      window.
                                                                                      open(
                                                                                      'https://javascript.info')
                                                                                      ;
                                                                                    
                                                                                    
                                                                                    
                                                                                      // popup allowed
                                                                                    
                                                                                    
                                                                                      button.
                                                                                      onclick
                                                                                      = () 
                                                                                      =>
                                                                                      {
                                                                                    
                                                                                    
                                                                                      window.
                                                                                      open(
                                                                                      'https://javascript.info')
                                                                                      ;
                                                                                    
                                                                                    
                                                                                      };
                                                                                    
                                                                                  
                                                                                

This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally.

What if the popup opens from onclick, but after setTimeout? That's a bit tricky.

Try this code:

js run // open after 3 seconds setTimeout(() => window.open('http://google.com'), 3000);

The popup opens in Chrome, but gets blocked in Firefox.

…If we decrease the delay, the popup works in Firefox too:

js run // open after 1 seconds setTimeout(() => window.open('http://google.com'), 1000);

The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it - removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not.

window.open

The syntax to open a popup is: window.open(url, name, params):

url
An URL to load into the new window.
name
A name of the new window. Each window has a window.name, and here we can specify which window to use for the popup. If there's already a window with such name - the given URL opens in it, otherwise a new window is opened.
params
The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: width=200,height=100.

Settings for params:

There is also a number of less supported browser-specific features, which are usually not used. Check window.open in MDN for examples.

Example: a minimalistic window

Let's open a window with minimal set of features, just to see which of them browser allows to disable:

`` js run let params =scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, width=0,height=0,left=-1000,top=-1000`;

open(‘/''', ‘test', params);

Here most "window features" are disabled and window is positioned offscreen. Run it and see what really happens. Most browsers "fix" odd things like zero width/height and offscreen left/top. For instance, Chrome open such a window with full width/height, so that it occupies the full screen.

Let's add normal positioning options and reasonable width, height, left, top coordinates:

`` js run let params =scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, width=600,height=300,left=100,top=100`;

open(‘/''', ‘test', params);

Most browsers show the example above as required.

Rules for omitted settings:

Accessing popup from window

The open call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more.

In this example, we generate popup content from JavaScript:

                                                                                  
                                                                                    
                                                                                      let newWin 
                                                                                      =
                                                                                      window.
                                                                                      open(
                                                                                      "about:blank"
                                                                                      ,
                                                                                      "hello"
                                                                                      ,
                                                                                      "width=200,height=200")
                                                                                      ;
                                                                                    
                                                                                    
                                                                                    
                                                                                      newWin.
                                                                                      document.
                                                                                      write(
                                                                                      "Hello, world!")
                                                                                      ;
                                                                                    
                                                                                  
                                                                                

And here we modify the contents after loading:

run let newWindow = open(‘/''', ‘example', ‘width=300,height=300') newWindow.focus();

alert(newWindow.location.href); // (*) about:blank, loading hasn't started yet

newWindow.onload = function() { let html = <div style="font-size:30px">Welcome!</div>; ! newWindow.document.body.insertAdjacentHTML(‘afterbegin', html); /! };

Please note: immediately after window.open, the new window isn't loaded yet. That's demonstrated by alert in line (*). So we wait for onload to modify it. We could also use DOMContentLoaded handler for newWin.document.

warn header="Same origin policy" Windows may freely access content of each other only if they come from the same origin (the same protocol://domain:port).

Otherwise, e.g. if the main window is from site.com, and the popup from gmail.com, that's impossible for user safety reasons. For the details, see chapter info:cross-window-communication.

Accessing window from popup

A popup may access the "opener" window as well using window.opener reference. It is null for all windows except popups.

If you run the code below, it replaces the opener (current) window content with "Test":

run let newWin = window.open("about:blank", "hello", "width=200,height=200");

newWin.document.write( "

The code above shows errors for any operations except:

Contrary to that, if the <iframe> has the same origin, we can do anything with it:

run

`` smart header="iframe.onload vsiframe.contentWindow.onload " Theiframe.onload event (on the

We shouldn't work with the document of a not-yet-loaded iframe, because that's the wrong document. If we set any event handlers on it, they will be ignored.

How to detect the moment when the document is there?

The right document is definitely at place when iframe.onload triggers. But it only triggers when the whole iframe with all resources is loaded.

We can try to catch the moment earlier using checks in setInterval:

run

Collection: window.frames

An alternative way to get a window object for <iframe> - is to get it from the named collection window.frames:

For instance:

run

An iframe may have other iframes inside. The corresponding window objects form a hierarchy.

Navigation links are:

For instance:

js run window.frames[0].parent === window; // true

We can use the top property to check if the current document is open inside a frame or not:

js run if (window == top) { // current window == window.top? alert('The script is in the topmost window, not in a frame'); } else { alert('The script runs in a frame!'); }

The "sandbox" iframe attribute

The sandbox attribute allows for the exclusion of certain actions inside an <iframe> in order to prevent it executing untrusted code. It "sandboxes" the iframe by treating it as coming from another origin and/or applying other limitations.

There's a "default set" of restrictions applied for <iframe sandbox src="...">. But it can be relaxed if we provide a space-separated list of restrictions that should not be applied as a value of the attribute, like this: <iframe sandbox="allow-forms allow-popups">.

In other words, an empty "sandbox" attribute puts the strictest limitations possible, but we can put a space-delimited list of those that we want to lift.

Here's a list of limitations:

allow-same-origin
By default "sandbox" forces the "different origin" policy for the iframe. In other words, it makes the browser to treat the iframe as coming from another origin, even if its src points to the same site. With all implied restrictions for scripts. This option removes that feature.
allow-top-navigation
Allows the iframe to change parent.location.
allow-forms
Allows to submit forms from iframe.
allow-scripts
Allows to run scripts from the iframe.
allow-popups
Allows to window.open popups from the iframe

See the manual for more.

The example below demonstrates a sandboxed iframe with the default set of restrictions: <iframe sandbox src="...">. It has some JavaScript and a form.

Please note that nothing works. So the default set is really harsh:

[codetabs src="sandbox" height=140]

                                                                                    The purpose of the `"sandbox"` attribute is only to *add more* restrictions. It cannot remove them. In particular, it can't relax same-origin restrictions if the iframe comes from another origin.
                                                                                  

Cross-window messaging

The postMessage interface allows windows to talk to each other no matter which origin they are from.

So, it's a way around the "Same Origin" policy. It allows a window from john-smith.com to talk to gmail.com and exchange information, but only if they both agree and call corresponding JavaScript functions. That makes it safe for users.

The interface has two parts.

postMessage

The window that wants to send a message calls postMessage method of the receiving window. In other words, if we want to send the message to win, we should call win.postMessage(data, targetOrigin).

Arguments:

data
The data to send. Can be any object, the data is cloned using the "structured serialization algorithm". IE supports only strings, so we should JSON.stringify complex objects to support that browser.
targetOrigin
Specifies the origin for the target window, so that only a window from the given origin will get the message.

The targetOrigin is a safety measure. Remember, if the target window comes from another origin, we can't read it's location in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it.

Specifying targetOrigin ensures that the window only receives the data if it's still at the right site. Important when the data is sensitive.

For instance, here win will only receive the message if it has a document from the origin http://example.com:

no-beautify

/!

…And you're cool (I'm a cool hacker actually)!

The full demo of the attack:

[codetabs src="clickjacking-visible" height=160]

Here we have a half-transparent <iframe src="facebook.html">, and in the example we can see it hovering over the button. A click on the button actually clicks on the iframe, but that's not visible to the user, because the iframe is transparent.

As a result, if the visitor is authorized on Facebook ("remember me" is usually turned on), then it adds a "Like". On Twitter that would be a "Follow" button.

Here's the same example, but closer to reality, with opacity:0 for <iframe>:

[codetabs src="clickjacking" height=160]

All we need to attack - is to position the <iframe> on the evil page in such a way that the button is right over the link. So that when a user clicks the link, they actually click the button. That's usually doable with CSS.

smart header="Clickjacking is for clicks, not for keyboard" The attack only affects mouse actions (or similar, like taps on mobile).

Keyboard input is much difficult to redirect. Technically, if we have a text field to hack, then we can position an iframe in such a way that text fields overlap each other. So when a visitor tries to focus on the input they see on the page, they actually focus on the input inside the iframe.

But then there's a problem. Everything that the visitor types will be hidden, because the iframe is not visible.

People will usually stop typing when they can't see their new characters printing on the screen.

Old-school defences (weak)

The oldest defence is a bit of JavaScript which forbids opening the page in a frame (so-called "framebusting").

That looks like this:

                                                                                          
                                                                                            
                                                                                              if (top 
                                                                                              != window) 
                                                                                              {
                                                                                            
                                                                                            
                                                                                              top.
                                                                                              location
                                                                                              =
                                                                                              window.
                                                                                              location
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              }
                                                                                            
                                                                                          
                                                                                        

That is: if the window finds out that it's not on top, then it automatically makes itself the top.

This not a reliable defence, because there are many ways to hack around it. Let's cover a few.

Blocking top-navigation

We can block the transition caused by changing top.location in beforeunload event handler.

The top page (enclosing one, belonging to the hacker) sets a preventing handler to it, like this:

                                                                                          
                                                                                            
                                                                                              window.
                                                                                              onbeforeunload
                                                                                              =
                                                                                              function() 
                                                                                              {
                                                                                            
                                                                                            
                                                                                              return
                                                                                              false
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              };
                                                                                            
                                                                                          
                                                                                        

When the iframe tries to change top.location, the visitor gets a message asking them whether they want to leave.

In most cases the visitor would answer negatively because they don't know about the iframe - all they can see is the top page, there's no reason to leave. So top.location won't change!

In action:

[codetabs src="top-location"]

Sandbox attribute

One of the things restricted by the sandbox attribute is navigation. A sandboxed iframe may not change top.location.

So we can add the iframe with sandbox="allow-scripts allow-forms". That would relax the restrictions, permitting scripts and forms. But we omit allow-top-navigation so that changing top.location is forbidden.

Here's the code:

                                                                                          
                                                                                            
                                                                                              <iframe
                                                                                               *
                                                                                              !*sandbox
                                                                                              =
                                                                                              "allow-scripts allow-forms"
                                                                                              */!*
                                                                                               src=
                                                                                              "facebook.html"
                                                                                              ></iframe>
                                                                                            
                                                                                          
                                                                                        

There are other ways to work around that simple protection too.

X-Frame-Options

The server-side header X-Frame-Options can permit or forbid displaying the page inside a frame.

It must be sent exactly as HTTP-header: the browser will ignore it if found in HTML <meta> tag. So, <meta http-equiv="X-Frame-Options"...> won't do anything.

The header may have 3 values:

DENY
Never ever show the page inside a frame.
SAMEORIGIN
Allow inside a frame if the parent document comes from the same origin.
ALLOW-FROM domain
Allow inside a frame if the parent document is from the given domain.

For instance, Twitter uses X-Frame-Options: SAMEORIGIN.

                                                                                        Here's the result:
<iframe src="https://twitter.com"></iframe>
<!-- ebook: prerender/ chrome headless dies and timeouts on this iframe -->
<iframe src="https://twitter.com"></iframe>
Depending on your browser, the `iframe` above is either empty or alerting you that the browser won't permit that page to be navigating in this way.
                                                                                      

Showing with disabled functionality

The X-Frame-Options header has a side-effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so.

So there are other solutions… For instance, we can "cover" the page with a <div> with styles height: 100%; width: 100%;, so that it will intercept all clicks. That <div> is to be removed if window == top or if we figure out that we don't need the protection.

Something like this:

                                                                                          
                                                                                            
                                                                                              <style>
                                                                                            
                                                                                            
                                                                                              #protector {
                                                                                            
                                                                                            
                                                                                              height: 
                                                                                              100
                                                                                              %
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              width: 
                                                                                              100
                                                                                              %
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              position: 
                                                                                              absolute
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              left: 
                                                                                              0
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              top: 
                                                                                              0
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              z-index: 
                                                                                              99999999
                                                                                              ;
                                                                                            
                                                                                              }
                                                                                            
                                                                                              </style>
                                                                                            
                                                                                            
                                                                                            
                                                                                              <div
                                                                                               id=
                                                                                              "protector"
                                                                                              >
                                                                                            
                                                                                            
                                                                                              <a
                                                                                               href=
                                                                                              "/"
                                                                                               target=
                                                                                              "_blank"
                                                                                              >Go to the site
                                                                                              </a>
                                                                                            
                                                                                            
                                                                                              </div>
                                                                                            
                                                                                            
                                                                                            
                                                                                              <script>
                                                                                            
                                                                                            
                                                                                              // there will be an error if top window is from the different origin
                                                                                            
                                                                                            
                                                                                              // but that's ok here
                                                                                            
                                                                                            
                                                                                              if (
                                                                                              top.
                                                                                              document.
                                                                                              domain
                                                                                              ==
                                                                                              document.
                                                                                              domain) 
                                                                                              {
                                                                                            
                                                                                            
                                                                                              protector.
                                                                                              remove()
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              }
                                                                                            
                                                                                            
                                                                                              </script>
                                                                                            
                                                                                          
                                                                                        

The demo:

[codetabs src="protector"]

The samesite cookie attribute can also prevent clickjacking attacks.

A cookie with such attribute is only sent to a website if it's opened directly, not via a frame, or otherwise. More information in the chapter info:cookie#samesite.

If the site, such as Facebook, had samesite attribute on its authentication cookie, like this:

                                                                                        Set-Cookie: authorization=secret; samesite
                                                                                      

…Then such cookie wouldn't be sent when Facebook is open in iframe from another site. So the attack would fail.

The samesite cookie attribute will not have an effect when cookies are not used. This may allow other websites to easily show our public, unauthenticated pages in iframes.

However, this may also allow clickjacking attacks to work in a few limited cases. An anonymous polling website that prevents duplicate voting by checking IP addresses, for example, would still be vulnerable to clickjacking because it does not authenticate users using cookies.

Summary

Clickjacking is a way to "trick" users into clicking on a victim site without even knowing what's happening. That's dangerous if there are important click-activated actions.

A hacker can post a link to their evil page in a message, or lure visitors to their page by some other means. There are many variations.

From one perspective - the attack is "not deep": all a hacker is doing is intercepting a single click. But from another perspective, if the hacker knows that after the click another control will appear, then they may use cunning messages to coerce the user into clicking on them as well.

The attack is quite dangerous, because when we engineer the UI we usually don't anticipate that a hacker may click on behalf of the visitor. So vulnerabilities can be found in totally unexpected places.

ArrayBuffer, binary arrays

In web-development we meet binary data mostly while dealing with files (create, upload, download). Another typical use case is image processing.

That's all possible in JavaScript, and binary operations are high-performant.

Although, there's a bit of confusion, because there are many classes. To name a few: - ArrayBuffer, Uint8Array, DataView, Blob, File, etc.

Binary data in JavaScript is implemented in a non-standard way, compared to other languages. But when we sort things out, everything becomes fairly simple.

The basic binary object is ArrayBuffer - a reference to a fixed-length contiguous memory area.

We create it like this: js run let buffer = new ArrayBuffer(16); // create a buffer of length 16 alert(buffer.byteLength); // 16

This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.

warn header="`ArrayBuffer` is not an array of something" Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in common with `Array`: - It has a fixed length, we can't increase or decrease it. - It takes exactly that much space in the memory. - To access individual bytes, another "view" object is needed, not `buffer[index]`.

ArrayBuffer is a memory area. What's stored in it? It has no clue. Just a raw sequence of bytes.

To manipulate an ArrayBuffer, we need to use a "view" object.

A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the ArrayBuffer.

For instance:

So, the binary data in an ArrayBuffer of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers (2 bytes each), or 4 even bigger (4 bytes each), or 2 floating-point values with high precision (8 bytes each).

ArrayBuffer is the core object, the root of everything, the raw binary data.

But if we're going to write into it, or iterate over it, basically for almost any operation - we must use a view, e.g:

run let buffer = new ArrayBuffer(16); // create a buffer of length 16

! let view = new Uint32Array(buffer); // treat buffer as a sequence of 32-bit integers

alert(Uint32Array.BYTES_PER_ELEMENT); // 4 bytes per integer /!

alert(view.length); // 4, it stores that many integers alert(view.byteLength); // 16, the size in bytes

// let's write a value view[0] = 123456;

// iterate over values for(let num of view) { alert(num); // 123456, then 0, 0, 0 (4 values total) }

TypedArray

The common term for all these views ( Uint8Array, Uint32Array, etc) is TypedArray. They share the same set of methods and properities.

Please note, there's no constructor called TypedArray, it's just a common "umbrella" term to represent one of views over ArrayBuffer: Int8Array, Uint8Array and so on, the full list will soon follow.

When you see something like new TypedArray, it means any of new Int8Array, new Uint8Array, etc.

Typed arrays behave like regular arrays: have indexes and are iterable.

A typed array constructor (be it Int8Array or Float64Array, doesn't matter) behaves differently depending on argument types.

There are 5 variants of arguments:

                                                                                          
                                                                                            
                                                                                              new
                                                                                              TypedArray(buffer
                                                                                              , [byteOffset]
                                                                                              , [length])
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              new
                                                                                              TypedArray(object)
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              new
                                                                                              TypedArray(typedArray)
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              new
                                                                                              TypedArray(length)
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              new
                                                                                              TypedArray()
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        
  1. If an ArrayBuffer argument is supplied, the view is created over it. We used that syntax already.

    Optionally we can provide byteOffset to start from (0 by default) and the length (till the end of the buffer by default), then the view will cover only a part of the buffer.

  2. If an Array, or any array-like object is given, it creates a typed array of the same length and copies the content.

    We can use it to pre-fill the array with the data: js run *!* let arr = new Uint8Array([0, 1, 2, 3]); */!* alert( arr.length ); // 4, created binary array of the same length alert( arr[1] ); // 1, filled with 4 bytes (unsigned 8-bit integers) with given values
  3. If another TypedArray is supplied, it does the same: creates a typed array of the same length and copies values. Values are converted to the new type in the process, if needed. js run let arr16 = new Uint16Array([1, 1000]); *!* let arr8 = new Uint8Array(arr16); */!* alert( arr8[0] ); // 1 alert( arr8[1] ); // 232, tried to copy 1000, but can't fit 1000 into 8 bits (explanations below)

  4. For a numeric argument length - creates the typed array to contain that many elements. Its byte length will be length multiplied by the number of bytes in a single item TypedArray.BYTES_PER_ELEMENT: js run let arr = new Uint16Array(4); // create typed array for 4 integers alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per integer alert( arr.byteLength ); // 8 (size in bytes)

  5. Without arguments, creates an zero-length typed array.

We can create a TypedArray directly, without mentioning ArrayBuffer. But a view cannot exist without an underlying ArrayBuffer, so gets created automatically in all these cases except the first one (when provided).

To access the ArrayBuffer, there are properties: - arr.buffer - references the ArrayBuffer. - arr.byteLength - the length of the ArrayBuffer.

So, we can always move from one view to another:

                                                                                          
                                                                                            
                                                                                              let arr8 
                                                                                              =
                                                                                              new
                                                                                              Uint8Array([
                                                                                              0
                                                                                              ,
                                                                                              1
                                                                                              ,
                                                                                              2
                                                                                              ,
                                                                                              3])
                                                                                              ;
                                                                                            
                                                                                            
                                                                                            
                                                                                              // another view on the same data
                                                                                            
                                                                                            
                                                                                              let arr16 
                                                                                              =
                                                                                              new
                                                                                              Uint16Array(
                                                                                              arr8.
                                                                                              buffer)
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

Here's the list of typed arrays:

`` warn header="Noint8 or similar single-valued types" Please note, despite of the names likeInt8Array , there's no single-value type likeint , orint8` in JavaScript.

That's logical, as Int8Array is not an array of these individual values, but rather a view on ArrayBuffer.

Out-of-bounds behavior

What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But extra bits are cut-off.

For instance, let's try to put 256 into Uint8Array. In binary form, 256 is 100000000 (9 bits), but Uint8Array only provides 8 bits per value, that makes the available range from 0 to 255.

For bigger numbers, only the rightmost (less significant) 8 bits are stored, and the rest is cut off:

So we'll get zero.

For 257, the binary form is 100000001 (9 bits), the rightmost 8 get stored, so we'll have 1 in the array:

In other words, the number modulo 2 8 is saved.

Here's the demo:

run let uint8array = new Uint8Array(16);

let num = 256; alert(num.toString(2)); // 100000000 (binary representation)

uint8array[0] = 256; uint8array[1] = 257;

alert(uint8array[0]); // 0 alert(uint8array[1]); // 1

Uint8ClampedArray is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is useful for image processing.

TypedArray methods

TypedArray has regular Array methods, with notable exceptions.

We can iterate, map, slice, find, reduce etc.

There are few things we can't do though:

There are two additional methods:

These methods allow us to copy typed arrays, mix them, create new arrays from existing ones, and so on.

DataView

DataView is a special super-flexible "untyped" view over ArrayBuffer. It allows to access the data on any offset in any format.

The syntax:

                                                                                          
                                                                                            
                                                                                              new
                                                                                              DataView(buffer
                                                                                              , [byteOffset]
                                                                                              , [byteLength])
                                                                                            
                                                                                          
                                                                                        

For instance, here we extract numbers in different formats from the same buffer:

run // binary array of 4 bytes, all have the maximal value 255 let buffer = new Uint8Array([255, 255, 255, 255]).buffer;

let dataView = new DataView(buffer);

// get 8-bit number at offset 0 alert( dataView.getUint8(0) ); // 255

// now get 16-bit number at offset 0, it consists of 2 bytes, together interpreted as 65535 alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)

// get 32-bit number at offset 0 alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)

dataView.setUint32(0, 0); // set 4-byte number to zero, thus setting all bytes to 0

DataView is great when we store mixed-format data in the same buffer. For example, when we store a sequence of pairs (16-bit integer, 32-bit float), DataView allows to access them easily.

Summary

ArrayBuffer is the core object, a reference to the fixed-length contiguous memory area.

To do almost any operation on ArrayBuffer, we need a view.

In most cases we create and operate directly on typed arrays, leaving ArrayBuffer under cover, as a "common denominator". We can access it as .buffer and make another view if needed.

There are also two additional terms, that are used in descriptions of methods that operate on binary data: - ArrayBufferView is an umbrella term for all these kinds of views. - BufferSource is an umbrella term for ArrayBuffer or ArrayBufferView.

We'll see these terms in the next chapters. BufferSource is one of the most common terms, as it means "any kind of binary data" - an ArrayBuffer or a view over it.

Here's a cheatsheet:

TextDecoder and TextEncoder

What if the binary data is actually a string? For instance, we received a file with textual data.

The build-in TextDecoder object allows to read the value into an actual JavaScript string, given the buffer and the encoding.

We first need to create it:

                                                                                          
                                                                                            
                                                                                              let decoder 
                                                                                              =
                                                                                              new
                                                                                              TextDecoder([label]
                                                                                              , [options])
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

…And then decode:

                                                                                          
                                                                                            
                                                                                              let str 
                                                                                              =
                                                                                              decoder.
                                                                                              decode([input]
                                                                                              , [options])
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

For instance:

run let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);

alert( new TextDecoder().decode(uint8Array) ); // Hello

run let uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);

alert( new TextDecoder().decode(uint8Array) ); // 你好

We can decode a part of the buffer by creating a subarray view for it:

run let uint8Array = new Uint8Array([0, 72, 101, 108, 108, 111, 0]);

// the string is in the middle // create a new view over it, without copying anything let binaryString = uint8Array.subarray(1, -1);

alert( new TextDecoder().decode(binaryString) ); // Hello

TextEncoder

TextEncoder does the reverse thing - converts a string into bytes.

The syntax is:

                                                                                          
                                                                                            
                                                                                              let encoder 
                                                                                              =
                                                                                              new
                                                                                              TextEncoder()
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

The only encoding it supports is "utf-8".

It has two methods: - encode(str) - returns Uint8Array from a string. - encodeInto(str, destination) - encodes str into destination that must be Uint8Array.

run let encoder = new TextEncoder();

let uint8Array = encoder.encode("Hello"); alert(uint8Array); // 72,101,108,108,111

Comparisons

We know many comparison operators from maths.

In JavaScript they are written like this:

In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities.

At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues.

Boolean is the result

All comparison operators return a boolean value:

For example:

js run alert( 2 > 1 ); // true (correct) alert( 2 == 1 ); // false (wrong) alert( 2 != 1 ); // true (correct)

A comparison result can be assigned to a variable, just like any value:

js run let result = 5 > 4; // assign the result of the comparison alert( result ); // true

String comparison

To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order.

In other words, strings are compared letter-by-letter.

For example:

js run alert( 'Z' > 'A' ); // true alert( 'Glow' > 'Glee' ); // true alert( 'Bee' > 'Be' ); // true

The algorithm to compare two strings is simple:

  1. Compare the first character of both strings.
  2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done.
  3. Otherwise, if both strings' first characters are the same, compare the second characters the same way.
  4. Repeat until the end of either string.
  5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater.

In the first example above, the comparison 'Z' > 'A' gets to a result at the first step.

The second comparison 'Glow' and 'Glee' needs more steps as strings are compared character-by-character:

  1. G is the same as G.
  2. l is the same as l.
  3. o is greater than e. Stop here. The first string is greater.

smart header="Not a real dictionary, but Unicode order" The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same.

For instance, case matters. A capital letter "A" is not equal to the lowercase "a". Which one is greater? The lowercase "a". Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter info:string.

Comparison of different types

When comparing values of different types, JavaScript converts the values to numbers.

For example:

js run alert( '2' > 1 ); // true, string '2' becomes a number 2 alert( '01' == 1 ); // true, string '01' becomes a number 1

For boolean values, true becomes 1 and false becomes 0.

For example:

js run alert( true == 1 ); // true alert( false == 0 ); // true

smart header="A funny consequence" It is possible that at the same time:

For example:

run let a = 0; alert( Boolean(a) ); // false

let b = "0"; alert( Boolean(b) ); // true

alert(a == b); // true!

                                                                                        
From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules.
                                                                                      

Strict equality

A regular equality check == has a problem. It cannot differentiate 0 from false:

js run alert( 0 == false ); // true

The same thing happens with an empty string:

js run alert( '' == false ); // true

This happens because operands of different types are converted to numbers by the equality operator ==. An empty string, just like false, becomes a zero.

What to do if we'd like to differentiate 0 from false?

A strict equality operator === checks the equality without type conversion.

In other words, if a and b are of different types, then a === b immediately returns false without an attempt to convert them.

Let's try it:

js run alert( 0 === false ); // false, because the types are different

There is also a "strict non-equality" operator !== analogous to !=.

The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors.

Comparison with null and undefined

There's a non-intuitive behavior when null or undefined are compared to other values.

For a strict equality check ===

These values are different, because each of them is a different type.

js run alert( null === undefined ); // false

For a non-strict check ==

There's a special rule. These two are a "sweet couple": they equal each other (in the sense of ==), but not any other value.

js run alert( null == undefined ); // true

For maths and other comparisons < > <= >=
null/undefined are converted to numbers: null becomes 0, while undefined becomes NaN.

Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them.

Strange result: null vs 0

Let's compare null with a zero:

js run alert( null > 0 ); // (1) false alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) *!*true*/!*

Mathematically, that's strange. The last result states that " null is greater than or equal to zero", so in one of the comparisons above it must be true, but they are both false.

The reason is that an equality check == and comparisons > < >= <= work differently. Comparisons convert null to a number, treating it as 0. That's why (3) null >= 0 is true and (1) null > 0 is false.

On the other hand, the equality check == for undefined and null is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) null == 0 is false.

An incomparable undefined

The value undefined shouldn't be compared to other values:

js run alert( undefined > 0 ); // false (1) alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3)

Why does it dislike zero so much? Always false!

We get these results because:

Avoid problems

Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them:

Summary

Blob

ArrayBuffer and views are a part of ECMA standard, a part of JavaScript.

In the browser, there are additional higher-level objects, described in File API, in particular Blob.

Blob consists of an optional string type (a MIME-type usually), plus blobParts - a sequence of other Blob objects, strings and BufferSource.

The constructor syntax is:

                                                                                          
                                                                                            
                                                                                              new
                                                                                              Blob(blobParts
                                                                                              , options)
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

For example:

                                                                                          
                                                                                            
                                                                                              // create Blob from a string
                                                                                            
                                                                                            
                                                                                              let blob 
                                                                                              =
                                                                                              new
                                                                                              Blob([
                                                                                              "<html>…</html>"]
                                                                                              ,
                                                                                              {
                                                                                              type
                                                                                              :
                                                                                              'text/html'
                                                                                              })
                                                                                              ;
                                                                                            
                                                                                            
                                                                                              // please note: the first argument must be an array [...]
                                                                                            
                                                                                          
                                                                                        
                                                                                          
                                                                                            
                                                                                              // create Blob from a typed array and strings
                                                                                            
                                                                                            
                                                                                              let hello 
                                                                                              =
                                                                                              new
                                                                                              Uint8Array([
                                                                                              72
                                                                                              ,
                                                                                              101
                                                                                              ,
                                                                                              108
                                                                                              ,
                                                                                              108
                                                                                              ,
                                                                                              111])
                                                                                              ;
                                                                                              // "Hello" in binary form
                                                                                            
                                                                                            
                                                                                            
                                                                                              let blob 
                                                                                              =
                                                                                              new
                                                                                              Blob([hello
                                                                                              ,
                                                                                              ' '
                                                                                              ,
                                                                                              'world']
                                                                                              ,
                                                                                              {
                                                                                              type
                                                                                              :
                                                                                              'text/plain'
                                                                                              })
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

We can extract Blob slices with:

                                                                                          
                                                                                            
                                                                                              blob.
                                                                                              slice([byteStart]
                                                                                              , [byteEnd]
                                                                                              , [contentType])
                                                                                              ;
                                                                                            
                                                                                          
                                                                                        

The arguments are similar to array.slice, negative numbers are allowed too.

`` smart header="Blob objects are immutable" We can't change data directly in aBlob , but we can slice parts of aBlob , create newBlob objects from them, mix them into a newBlob` and so on.

This behavior is similar to JavaScript strings: we can't change a character in a string, but we can make a new corrected string.

Blob as URL

A Blob can be easily used as a URL for <a>, <img> or other tags, to show its contents.

Thanks to type, we can also download/upload Blob objects, and the type naturally becomes Content-Type in network requests.

Let's start with a simple example. By clicking on a link you download a dynamically-generated Blob with hello world contents as a file:

run Download

We can also create a link dynamically in JavaScript and simulate a click by link.click(), then download starts automatically.

Here's the similar code that causes user to download the dynamically created Blob, without any HTML:

run let link = document.createElement(‘a'); link.download = ‘hello.txt';

let blob = new Blob([‘Hello, world!'''], {type: ‘text/plain'});

link.href = URL.createObjectURL(blob);

link.click();

URL.revokeObjectURL(link.href);

                                                                                        
`URL.createObjectURL` takes a `Blob` and creates a unique URL for it, in the form `blob:<origin>/<uuid>`.
That's what the value of `link.href` looks like:

                                                                                      

blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273

For each URL generated by URL.createObjectURL the browser stores a URL -> Blob mapping internally. So such URLs are short, but allow to access the Blob.

A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the Blob in <img>, <a>, basically any other object that expects a URL.

There's a side-effect though. While there's a mapping for a Blob, the Blob itself resides in the memory. The browser can't free it.

The mapping is automatically cleared on document unload, so Blob objects are freed then. But if an app is long-living, then that doesn't happen soon.

So if we create a URL, that Blob will hang in memory, even if not needed any more.

URL.revokeObjectURL(url) removes the reference from the internal mapping, thus allowing the Blob to be deleted (if there are no other references), and the memory to be freed.

In the last example, we intend the Blob to be used only once, for instant downloading, so we call URL.revokeObjectURL(link.href) immediately.

In the previous example with the clickable HTML-link, we don't call URL.revokeObjectURL(link.href), because that would make the Blob url invalid. After the revocation, as the mapping is removed, the URL doesn't work any more.

Blob to base64

An alternative to URL.createObjectURL is to convert a Blob into a base64-encoded string.

That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important - we can use this encoding in "data-urls".

A data url has the form data:[<mediatype>][;base64],<data>. We can use such urls everywhere, on par with "regular" urls.

For instance, here's a smiley:

                                                                                          
                                                                                            
                                                                                              <img
                                                                                               src=
                                                                                              "data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7"
                                                                                              >
                                                                                            
                                                                                          
                                                                                        

The browser will decode the string and show the image:

To transform a Blob into base64, we'll use the built-in FileReader object. It can read data from Blobs in multiple formats. In the next chapter we'll cover it more in-depth.

Here's the demo of downloading a blob, now via base-64:

run let link = document.createElement(‘a'); link.download = ‘hello.txt';

let blob = new Blob([‘Hello, world!'''], {type: ‘text/plain'});

! let reader = new FileReader(); reader.readAsDataURL(blob); // converts the blob to base64 and calls onload /!

reader.onload = function() { link.href = reader.result; // data url link.click(); };

Both ways of making a URL of a Blob are usable. But usually URL.createObjectURL(blob) is simpler and faster.

compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url" + We need to revoke them if care about memory. + Direct access to blob, no "encoding/decoding" - No need to revoke anything. - Performance and memory losses on big `Blob` objects for encoding.

Image to blob

We can create a Blob of an image, an image part, or even make a page screenshot. That's handy to upload it somewhere.

Image operations are done via <canvas> element:

  1. Draw an image (or its part) on canvas using canvas.drawImage.
  2. Call canvas method .toBlob(callback, format, quality) that creates a Blob and runs callback with it when done.

In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:

run // take any image let img = document.querySelector(‘img');

// make

of the same size let canvas = document.createElement(‘canvas'); canvas.width = img.clientWidth; canvas.height = img.clientHeight;

let context = canvas.getContext(‘2d');

// copy image to it (this method allows to cut image) context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas

// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement(‘a'); link.download = ‘example.png';

link.href = URL.createObjectURL(blob); link.click();

// delete the internal blob reference, to let the browser clear memory from it URL.revokeObjectURL(link.href); }, ‘image/png');

If we prefer async/await instead of callbacks:

For screenshotting a page, we can use a library such as https://github.com/niklasvh/html2canvas. What it does is just walks the page and draws it on <canvas>. Then we can get a Blob of it the same way as above.

From Blob to ArrayBuffer

The Blob constructor allows to create a blob from almost anything, including any BufferSource.

But if we need to perform low-level processing, we can get the lowest-level ArrayBuffer from it using FileReader:

Summary

While ArrayBuffer, Uint8Array and other BufferSource are "binary data", a Blob represents "binary data with type".

That makes Blobs convenient for upload/download operations, that are so common in the browser.

Methods that perform web-requests, such as XMLHttpRequest, fetch and so on, can work with Blob natively, as well as with other binary types.

We can easily convert between Blob and low-level binary data types:

File and FileReader

A File object inherits from Blob and is extended with filesystem-related capabilities.

There are two ways to obtain it.

First, there's a constructor, similar to Blob:

Second, more often we get a file from <input type="file"> or drag'n'drop or other browser interfaces. In that case, the file gets this information from OS.

As File inherits from Blob, File objects have the same properties, plus: - name - the file name, - lastModified - the timestamp of last modification.

That's how we can get a File object from <input type="file">:

run

                                                                                              The input may select multiple files, so `input.files` is an array-like object with them. Here we have only one file, so we just take `input.files[0]`.
                                                                                            

FileReader

FileReader is an object with the sole purpose of reading data from Blob (and hence File too) objects.

It delivers the data using events, as reading from disk may take time.

The constructor:

The main methods:

The choice of read* method depends on which format we prefer, how we're going to use the data.

As the reading proceeds, there are events: - loadstart - loading started. - progress - occurs during reading. - load - no errors, reading complete. - abort - abort() called. - error - error has occurred. - loadend - reading finished with either success or failure.

When the reading is finished, we can access the result as: - reader.result is the result (if successful) - reader.error is the error (if failed).

The most widely used events are for sure load and error.

Here's an example of reading a file:

run

`` smart header="FileReader for blobs" As mentioned in the chapter <info:blob>,FileReader` can read not just files, but any blobs.

We can use it to convert a blob to another format: - readAsArrayBuffer(blob) - to ArrayBuffer, - readAsText(blob, [encoding]) - to string (an alternative to TextDecoder), - readAsDataURL(blob) - to base64 data url.

`` smart header="FileReaderSync is available inside Web Workers" For Web Workers, there also exists a synchronous variant ofFileReader`, called FileReaderSync.

Its reading methods read* do not generate events, but rather return a result, as regular functions do.

That's only inside a Web Worker though, because delays in synchronous calls, that are possible while reading from files, in Web Workers are less important. They do not affect the page.

Summary

File objects inherit from Blob.

In addition to Blob methods and properties, File objects also have name and lastModified properties, plus the internal ability to read from filesystem. We usually get File objects from user input, like <input> or Drag'n'Drop events ( ondragend).

FileReader objects can read from a file or a blob, in one of three formats: - String ( readAsText). - ArrayBuffer ( readAsArrayBuffer). - Data url, base-64 encoded ( readAsDataURL).

In many cases though, we don't have to read the file contents. Just as we did with blobs, we can create a short url with URL.createObjectURL(file) and assign it to <a> or <img>. This way the file can be downloaded or shown up as an image, as a part of canvas etc.

And if we're going to send a File over a network, that's also easy: network API like XMLHttpRequest or fetch natively accepts File objects.

Fetch

JavaScript can send network requests to the server and load new information whenever it's needed.

For example, we can use a network request to:

…And all of that without reloading the page!

There's an umbrella term "AJAX" (abbreviated Asynchronous JavaScript And XML) for network requests from JavaScript. We don't have to use XML though: the term comes from old times, that's why that word is there. You may have heard that term already.

There are multiple ways to send a network request and get information from the server.

The fetch() method is modern and versatile, so we'll start with it. It's not supported by old browsers (can be polyfilled), but very well supported among the modern ones.

The basic syntax is:

Without options, this is a simple GET request, downloading the contents of the url.

The browser starts the request right away and returns a promise that the calling code should use to get the result.

Getting a response is usually a two-stage process.

First, the promise, returned by fetch, resolves with an object of the built-in Response class as soon as the server responds with headers.

At this stage we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet.

The promise rejects if the fetch was unable to make HTTP-request, e.g. network problems, or there's no such site. Abnormal HTTP-statuses, such as 404 or 500 do not cause an error.

We can see HTTP-status in response properties:

For example:

Second, to get the response body, we need to use an additional method call.

Response provides multiple promise-based methods to access the body in various formats:

For instance, let's get a JSON-object with latest commits from GitHub:

run async let url = ‘https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'; let response = await fetch(url);

! let commits = await response.json(); // read response body and parse as JSON /!

alert(commits[0].author.login);

Or, the same without await, using pure promises syntax:

js run fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') .then(response => response.json()) .then(commits => alert(commits[0].author.login));

To get the response text, await response.text() instead of .json():

run async let response = await fetch(‘https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

let text = await response.text(); // read response body as text

alert(text.slice(0, 80) + ‘…');

As a show-case for reading in binary format, let's fetch and show a logo image of "fetch" specification (see chapter Blob for details about operations on Blob):

async run let response = await fetch(‘/article/fetch/logo-fetch.svg');

! let blob = await response.blob(); // download as Blob object /!

// create for it let img = document.createElement(‘img'); img.style = ‘position:fixed;top:10px;left:10px;width:100px'; document.body.append(img);

// show it img.src = URL.createObjectURL(blob);

setTimeout(() => { // hide after three seconds img.remove(); URL.revokeObjectURL(img.src); }, 3000);

                                                                                                  We can choose only one body-reading method.
If we've already got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed.
let text = await response.text(); // response body consumed
let parsed = await response.json(); // fails (already consumed)

                                                                                                

Response headers

The response headers are available in a Map-like headers object in response.headers.

It's not exactly a Map, but it has similar methods to get individual headers by name or iterate over them:

run async let response = await fetch(‘https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

// get one header alert(response.headers.get(‘Content-Type')); // application/json; charset=utf-8

// iterate over all headers for (let [key, value] of response.headers) { alert( ${key} = ${value}); }

Request headers

To set a request header in fetch, we can use the headers option. It has an object with outgoing headers, like this:

…But there's a list of forbidden HTTP headers that we can't set:

These headers ensure proper and safe HTTP, so they are controlled exclusively by the browser.

POST requests

To make a POST request, or a request with another method, we need to use fetch options:

The JSON format is used most of the time.

For example, this code submits user object as JSON:

run async let user = { name: ‘John', surname: ‘Smith' };

! let response = await fetch(‘/article/fetch/post/user', { method: ‘POST', headers: { ‘Content-Type': ‘application/json;charset=utf-8' }, body: JSON.stringify(user) }); /!

let result = await response.json(); alert(result.message);

Please note, if the request body is a string, then Content-Type header is set to text/plain;charset=UTF-8 by default.

But, as we're going to send JSON, we use headers option to send application/json instead, the correct Content-Type for JSON-encoded data.

Sending an image

We can also submit binary data with fetch using Blob or BufferSource objects.

In this example, there's a <canvas> where we can draw by moving a mouse over it. A click on the "submit" button sends the image to the server:

run autorun height="90"

Please note, here we don't set Content-Type header manually, because a Blob object has a built-in type (here image/png, as generated by toBlob). For Blob objects that type becomes the value of Content-Type.

The submit() function can be rewritten without async/await like this:

Summary

A typical fetch request consists of two await calls:

Or, without await:

Response properties: - response.status - HTTP code of the response, - response.ok - true is the status is 200-299. - response.headers - Map-like object with HTTP headers.

Methods to get response body: - response.text() - return the response as text, - response.json() - parse the response as JSON object, - response.formData() - return the response as FormData object (form/multipart encoding, see the next chapter), - response.blob() - return the response as Blob (binary data with type), - response.arrayBuffer() - return the response as ArrayBuffer (low-level binary data),

Fetch options so far: - method - HTTP-method, - headers - an object with request headers (not any header is allowed), - body - the data to send (request body) as string, FormData, BufferSource, Blob or UrlSearchParams object.

In the next chapters we'll see more options and use cases of fetch.

FormData

This chapter is about sending HTML forms: with or without files, with additional fields and so on.

FormData objects can help with that. As you might have guessed, it's the object to represent HTML form data.

The constructor is:

If HTML form element is provided, it automatically captures its fields.

The special thing about FormData is that network methods, such as fetch, can accept a FormData object as a body. It's encoded and sent out with Content-Type: multipart/form-data.

From the server point of view, that looks like a usual form submission.

Sending a simple form

Let's send a simple form first.

As you can see, that's almost one-liner:

run autorun

In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved".

FormData Methods

We can modify fields in FormData with methods:

A form is technically allowed to have many fields with the same name, so multiple calls to append add more same-named fields.

There's also method set, with the same syntax as append. The difference is that .set removes all fields with the given name, and then appends a new field. So it makes sure there's only one field with such name, the rest is just like append:

Also we can iterate over formData fields using for..of loop:

run let formData = new FormData(); formData.append(‘key1', ‘value1'); formData.append(‘key2', ‘value2');

// List key/value pairs for(let [name, value] of formData) { alert( ${name} = ${value}); // key1 = value1, then key2 = value2 }

Sending a form with a file

The form is always sent as Content-Type: multipart/form-data, this encoding allows to send files. So, <input type="file"> fields are sent also, similar to a usual form submission.

Here's an example with such form:

run autorun
Picture:

Sending a form with Blob data

As we've seen in the chapter info:fetch, it's easy to send dynamically generated binary data e.g. an image, as Blob. We can supply it directly as fetch parameter body.

In practice though, it's often convenient to send an image not separately, but as a part of the form, with additional fields, such as "name" and other metadata.

Also, servers are usually more suited to accept multipart-encoded forms, rather than raw binary data.

This example submits an image from <canvas>, along with some other fields, as a form, using FormData:

run autorun height="90"

Please note how the image Blob is added:

That's same as if there were <input type="file" name="image"> in the form, and the visitor submitted a file named "image.png" (3rd argument) with the data imageBlob (2nd argument) from their filesystem.

The server reads form data and the file, as if it were a regular form submission.

Summary

FormData objects are used to capture HTML form and submit it using fetch or another network method.

We can either create new FormData(form) from an HTML form, or create a object without a form at all, and then append fields with methods:

Let's note two peculiarities here:

  1. The set method removes fields with the same name, append doesn't. That's the only difference between them.
  2. To send a file, 3-argument syntax is needed, the last argument is a file name, that normally is taken from user filesystem for <input type="file">.

Other methods are:

That's it!

Fetch: Download progress

The fetch method allows to track download progress.

Please note: there's currently no way for fetch to track upload progress. For that purpose, please use XMLHttpRequest, we'll cover it later.

To track download progress, we can use response.body property. It's ReadableStream - a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the Streams API specification.

Unlike response.text(), response.json() and other methods, response.body gives full control over the reading process, and we can count how much is consumed at any moment.

Here's the sketch of code that reads the response from response.body:

The result of await reader.read() call is an object with two properties: - done - true when the reading is complete, otherwise false. - value - a typed array of bytes: Uint8Array.

                                                                                                                  Streams API also describes asynchronous iteration over `ReadableStream` with `for await..of` loop, but it's not yet widely supported (see [browser issues](https://github.com/whatwg/streams/issues/778#issuecomment-461341033)), so we use `while` loop.
                                                                                                                

We receive response chunks in the loop, until the loading finishes, that is: until done becomes true.

To log the progress, we just need for every received fragment value to add its length to the counter.

Here's the full working example that gets the response and logs the progress in console, more explanations to follow:

run async // Step 1: start the fetch and obtain a reader let response = await fetch(‘https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100');

const reader = response.body.getReader();

// Step 2: get total length const contentLength = +response.headers.get(‘Content-Length');

// Step 3: read the data let receivedLength = 0; // received that many bytes at the moment let chunks = []; // array of received binary chunks (comprises the body) while(true) { const {done, value} = await reader.read();

if (done) { break; }

chunks.push(value); receivedLength += value.length;

console.log( Received ${receivedLength} of ${contentLength}) }

// Step 4: concatenate chunks into single Uint8Array let chunksAll = new Uint8Array(receivedLength); // (4.1) let position = 0; for(let chunk of chunks) { chunksAll.set(chunk, position); // (4.2) position += chunk.length; }

// Step 5: decode into a string let result = new TextDecoder("utf-8").decode(chunksAll);

// We're done! let commits = JSON.parse(result); alert(commits[0].author.login);

Let's explain that step-by-step:

  1. We perform fetch as usual, but instead of calling response.json(), we obtain a stream reader response.body.getReader().

    Please note, we can't use both these methods to read the same response: either use a reader or a response method to get the result.
  2. Prior to reading, we can figure out the full response length from the Content-Length header.

    It may be absent for cross-origin requests (see chapter info:fetch-crossorigin) and, well, technically a server doesn't have to set it. But usually it's at place.
  3. Call await reader.read() until it's done.

    We gather response chunks in the array chunks. That's important, because after the response is consumed, we won't be able to "re-read" it using response.json() or another way (you can try, there'll be an error).
  4. At the end, we have chunks - an array of Uint8Array byte chunks. We need to join them into a single result. Unfortunately, there's no single method that concatenates those, so there's some code to do that:
    1. We create chunksAll = new Uint8Array(receivedLength) - a same-typed array with the combined length.
    2. Then use .set(chunk, position) method to copy each chunk one after another in it.
  5. We have the result in chunksAll. It's a byte array though, not a string.

    To create a string, we need to interpret these bytes. The built-in TextDecoder does exactly that. Then we can JSON.parse it, if necessary.

    What if we need binary content instead of a string? That's even simpler. Replace steps 4 and 5 with a single line that creates a Blob from all chunks: js let blob = new Blob(chunks);

At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process.

Once again, please note, that's not for upload progress (no way now with fetch), only for download progress.

Also, if the size is unknown, we should check receivedLength in the loop and break it once it reaches a certain limit. So that the chunks won't overflow the memory.

Fetch: Abort

As we know, fetch returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing fetch? E.g. if the user actions on our site indicate that the fetch isn't needed any more.

There's a special built-in object for such purposes: AbortController. It can be used to abort not only fetch, but other asynchronous tasks as well.

The usage is very straightforward:

The AbortController object

Create a controller:

A controller is an extremely simple object.

When abort() is called: - controller.signal emits the "abort" event. - controller.signal.aborted property becomes true.

Generally, we have two parties in the process: 1. The one that performs a cancelable operation, it sets a listener on controller.signal. 2. The one that cancels: it calls controller.abort() when needed.

Here's the full example (without fetch yet):

run let controller = new AbortController(); let signal = controller.signal;

// The party that performs a cancelable operation // gets the "signal" object // and sets the listener to trigger when controller.abort() is called signal.addEventListener(‘abort', () => alert("abort!"));

// The other party, that cancels (at any point later): controller.abort(); // abort!

// The event triggers and signal.aborted becomes true alert(signal.aborted); // true

As we can see, AbortController is just a mean to pass abort events when abort() is called on it.

We could implement the same kind of event listening in our code on our own, without the AbortController object.

But what's valuable is that fetch knows how to work with the AbortController object. It's integrated in it.

Using with fetch

To be able to cancel fetch, pass the signal property of an AbortController as a fetch option:

The fetch method knows how to work with AbortController. It will listen to abort events on signal.

Now, to abort, call controller.abort():

We're done: fetch gets the event from signal and aborts the request.

When a fetch is aborted, its promise rejects with an error AbortError, so we should handle it, e.g. in try..catch.

Here's the full example with fetch aborted after 1 second:

run async // abort in 1 second let controller = new AbortController(); setTimeout(() => controller.abort(), 1000);

try { let response = await fetch(‘/article/fetch-abort/demo/hang', { signal: controller.signal }); } catch(err) { if (err.name == ‘AbortError') { // handle abort() alert("Aborted!"); } else { throw err; } }

AbortController is scalable

AbortController is scalable. It allows to cancel multiple fetches at once.

Here's a sketch of code that fetches many urls in parallel, and uses a single controller to abort them all:

If we have our own asynchronous tasks, different from fetch, we can use a single AbortController to stop those, together with fetches.

We just need to listen to its abort event in our tasks:

Summary

Fetch: Cross-Origin Requests

If we send a fetch request to another web-site, it will probably fail.

For instance, let's try fetching http://example.com:

js run async try { await fetch('http://example.com'); } catch(err) { alert(err); // Failed to fetch }

Fetch fails, as expected.

The core concept here is origin - a domain/port/protocol triplet.

Cross-origin requests - those sent to another domain (even a subdomain) or protocol or port - require special headers from the remote side.

That policy is called "CORS": Cross-Origin Resource Sharing.

Why is CORS needed? A brief history

CORS exists to protect the internet from evil hackers.

Seriously. Let's make a very brief historical digression.

For many years a script from one site could not access the content of another site.

That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website hacker.com could not access the user's mailbox at website gmail.com. People felt safe.

JavaScript also did not have any special methods to perform network requests at that time. It was a toy language to decorate a web page.

But web developers demanded more power. A variety of tricks were invented to work around the limitation and make requests to other websites.

Using forms

One way to communicate with another server was to submit a <form> there. People submitted it into <iframe>, just to stay on the current page, like this:

So, it was possible to make a GET/POST request to another site, even without networking methods, as forms can send data anywhere. But as it's forbidden to access the content of an <iframe> from another site, it wasn't possible to read the response.

To be precise, there were actually tricks for that, they required special scripts at both the iframe and the page. So the communication with the iframe was technically possible. Right now there's no point to go into details, let these dinosaurs rest in peace.

Using scripts

Another trick was to use a script tag. A script could have any src, with any domain, like <script src="http://another.com/…">. It's possible to execute a script from any website.

If a website, e.g. another.com intended to expose data for this kind of access, then a so-called "JSONP (JSON with padding)" protocol was used.

Here's how it worked.

Let's say we, at our site, need to get the data from http://another.com, such as the weather:

  1. First, in advance, we declare a global function to accept the data, e.g. gotWeather.

  2. Then we make a <script> tag with src="http://another.com/weather.json?callback=gotWeather", using the name of our function as the callback URL-parameter.

  3. The remote server another.com dynamically generates a script that calls gotWeather(...) with the data it wants us to receive. js // The expected answer from the server looks like this: gotWeather({ temperature: 25, humidity: 78 });
  4. When the remote script loads and executes, gotWeather runs, and, as it's our function, we have the data.

That works, and doesn't violate security, because both sides agreed to pass the data this way. And, when both sides agree, it's definitely not a hack. There are still services that provide such access, as it works even for very old browsers.

After a while, networking methods appeared in browser JavaScript.

At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but with any new capabilities requiring an explicit allowance by the server, expressed in special headers.

Safe requests

There are two types of cross-origin requests:

  1. Safe requests.
  2. All the others.

Safe Requests are simpler to make, so let's start with them.

A request is safe if it satisfies two conditions:

  1. Safe method: GET, POST or HEAD
  2. Safe headers - the only allowed custom headers are:
    • Accept,
    • Accept-Language,
    • Content-Language,
    • Content-Type with the value application/x-www-form-urlencoded, multipart/form-data or text/plain.

Any other request is considered "unsafe". For instance, a request with PUT method or with an API-Key HTTP-header does not fit the limitations.

The essential difference is that a safe request can be made with a <form> or a <script>, without any special methods.

So, even a very old server should be ready to accept a safe request.

Contrary to that, requests with non-standard headers or e.g. method DELETE can't be created this way. For a long time JavaScript was unable to do such requests. So an old server may assume that such requests come from a privileged source, "because a webpage is unable to send them".

When we try to make a unsafe request, the browser sends a special "preflight" request that asks the server - does it agree to accept such cross-origin requests, or not?

And, unless the server explicitly confirms that with headers, an unsafe request is not sent.

Now we'll go into details.

CORS for safe requests

If a request is cross-origin, the browser always adds the Origin header to it.

For instance, if we request https://anywhere.com/request from https://javascript.info/page, the headers will look like:

                                                                                                                  GET /request
Host: anywhere.com
*!*
Origin: https://javascript.info
*/!*
...
                                                                                                                

As you can see, the Origin header contains exactly the origin (domain/protocol/port), without a path.

The server can inspect the Origin and, if it agrees to accept such a request, add a special header Access-Control-Allow-Origin to the response. That header should contain the allowed origin (in our case https://javascript.info), or a star *. Then the response is successful, otherwise it's an error.

The browser plays the role of a trusted mediator here: 1. It ensures that the correct Origin is sent with a cross-origin request. 2. It checks for permitting Access-Control-Allow-Origin in the response, if it exists, then JavaScript is allowed to access the response, otherwise it fails with an error.

Here's an example of a permissive server response:

                                                                                                                  200 OK
Content-Type:text/html; charset=UTF-8
*!*
Access-Control-Allow-Origin: https://javascript.info
*/!*
                                                                                                                

Response headers

For cross-origin request, by default JavaScript may only access so-called "safe" response headers:

Accessing any other response header causes an error.

                                                                                                                  There's no `Content-Length` header in the list!
This header contains the full response length. So, if we're downloading something and would like to track the percentage of progress, then an additional permission is required to access that header (see below).
                                                                                                                

To grant JavaScript access to any other response header, the server must send the Access-Control-Expose-Headers header. It contains a comma-separated list of unsafe header names that should be made accessible.

For example:

                                                                                                                  200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
*!*
Access-Control-Expose-Headers: Content-Length,API-Key
*/!*
                                                                                                                

With such an Access-Control-Expose-Headers header, the script is allowed to read the Content-Length and API-Key headers of the response.

"Unsafe" requests

We can use any HTTP-method: not just GET/POST, but also PATCH, DELETE and others.

Some time ago no one could even imagine that a webpage could make such requests. So there may still exist webservices that treat a non-standard method as a signal: "That's not a browser". They can take it into account when checking access rights.

So, to avoid misunderstandings, any "unsafe" request - that couldn't be done in the old times, the browser does not make such requests right away. First, it sends a preliminary, so-called "preflight" request, to ask for permission.

A preflight request uses the method OPTIONS, no body and two headers:

If the server agrees to serve the requests, then it should respond with empty body, status 200 and headers:

Let's see how it works step-by-step on the example of a cross-origin PATCH request (this method is often used to update data):

There are three reasons why the request is unsafe (one is enough): - Method PATCH - Content-Type is not one of: application/x-www-form-urlencoded, multipart/form-data, text/plain. - "Unsafe" API-Key header.

Step 1 (preflight request)

Prior to sending such a request, the browser, on its own, sends a preflight request that looks like this:

                                                                                                                  OPTIONS /service.json
Host: site.com
Origin: https://javascript.info
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type,API-Key
                                                                                                                

Step 2 (preflight response)

The server should respond with status 200 and the headers: - Access-Control-Allow-Origin: https://javascript.info - Access-Control-Allow-Methods: PATCH - Access-Control-Allow-Headers: Content-Type,API-Key.

That allows future communication, otherwise an error is triggered.

If the server expects other methods and headers in the future, it makes sense to allow them in advance by adding them to the list.

For example, this response also allows PUT, DELETE and additional headers:

                                                                                                                  200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Methods: PUT,PATCH,DELETE
Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
Access-Control-Max-Age: 86400
                                                                                                                

Now the browser can see that PATCH is in Access-Control-Allow-Methods and Content-Type,API-Key are in the list Access-Control-Allow-Headers, so it sends out the main request.

If there's the header Access-Control-Max-Age with a number of seconds, then the preflight permissions are cached for the given time. The response above will be cached for 86400 seconds (one day). Within this timeframe, subsequent requests will not cause a preflight. Assuming that they fit the cached allowances, they will be sent directly.

Step 3 (actual request)

When the preflight is successful, the browser now makes the main request. The process here is the same as for safe requests.

The main request has the Origin header (because it's cross-origin):

                                                                                                                  PATCH /service.json
Host: site.com
Content-Type: application/json
API-Key: secret
Origin: https://javascript.info
                                                                                                                

Step 4 (actual response)

The server should not forget to add Access-Control-Allow-Origin to the main response. A successful preflight does not relieve from that:

                                                                                                                  Access-Control-Allow-Origin: https://javascript.info
                                                                                                                

Then JavaScript is able to read the main server response.

                                                                                                                  Preflight request occurs "behind the scenes", it's invisible to JavaScript.
JavaScript only gets the response to the main request or an error if there's no server permission.
                                                                                                                

Credentials

A cross-origin request initiated by JavaScript code by default does not bring any credentials (cookies or HTTP authentication).

That's uncommon for HTTP-requests. Usually, a request to http://site.com is accompanied by all cookies from that domain. Cross-origin requests made by JavaScript methods on the other hand are an exception.

For example, fetch('http://another.com') does not send any cookies, even those (!) that belong to another.com domain.

Why?

That's because a request with credentials is much more powerful than without them. If allowed, it grants JavaScript the full power to act on behalf of the user and access sensitive information using their credentials.

Does the server really trust the script that much? Then it must explicitly allow requests with credentials with an additional header.

To send credentials in fetch, we need to add the option credentials: "include", like this:

Now fetch sends cookies originating from another.com with request to that site.

If the server agrees to accept the request with credentials, it should add a header Access-Control-Allow-Credentials: true to the response, in addition to Access-Control-Allow-Origin.

For example:

                                                                                                                  200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true
                                                                                                                

Please note: Access-Control-Allow-Origin is prohibited from using a star * for requests with credentials. Like shown above, it must provide the exact origin there. That's an additional safety measure, to ensure that the server really knows who it trusts to make such requests.

Summary

From the browser point of view, there are two kinds of cross-origin requests: "safe" and all the others.

"Safe" requests must satisfy the following conditions: - Method: GET, POST or HEAD. - Headers - we can set only: - Accept - Accept-Language - Content-Language - Content-Type to the value application/x-www-form-urlencoded, multipart/form-data or text/plain.

The essential difference is that safe requests were doable since ancient times using <form> or <script> tags, while unsafe were impossible for browsers for a long time.

So, the practical difference is that safe requests are sent right away, with the Origin header, while for the other ones the browser makes a preliminary "preflight" request, asking for permission.

For safe requests:

Additionally, to grant JavaScript access to any response headers except Cache-Control, Content-Language, Content-Type, Expires, Last-Modified or Pragma, the server should list the allowed ones in Access-Control-Expose-Headers header.

For unsafe requests, a preliminary "preflight" request is issued before the requested one:

Fetch API

So far, we know quite a bit about fetch.

Let's see the rest of API, to cover all its abilities.

                                                                                                                  Please note: most of these options are used rarely. You may skip this chapter and still use `fetch` well.
Still, it's good to know what `fetch` can do, so if the need arises, you can return and read the details.
                                                                                                                

Here's the full list of all possible fetch options with their default values (alternatives in comments):

An impressive list, right?

We fully covered method, headers and body in the chapter info:fetch.

The signal option is covered in info:fetch-abort.

Now let's explore the remaining capabilities.

referrer, referrerPolicy

These options govern how fetch sets the HTTP Referer header.

Usually that header is set automatically and contains the url of the page that made the request. In most scenarios, it's not important at all, sometimes, for security purposes, it makes sense to remove or shorten it.

The referrer option allows to set any Referer (within the current origin) or remove it.

To send no referer, set an empty string:

To set another url within the current origin:

The referrerPolicy option sets general rules for Referer.

Requests are split into 3 types:

  1. Request to the same origin.
  2. Request to another origin.
  3. Request from HTTPS to HTTP (from safe to unsafe protocol).

Unlike the referrer option that allows to set the exact Referer value, referrerPolicy tells the browser general rules for each request type.

Possible values are described in the Referrer Policy specification:

Here's a table with all combinations:

Value To same origin To another origin HTTPS→HTTP
"no-referrer" - - -
"no-referrer-when-downgrade" or "" (default) full full -
"origin" origin origin origin
"origin-when-cross-origin" full origin origin
"same-origin" full - -
"strict-origin" origin origin -
"strict-origin-when-cross-origin" full origin -
"unsafe-url" full full full

Let's say we have an admin zone with a URL structure that shouldn't be known from outside of the site.

If we send a fetch, then by default it always sends the Referer header with the full url of our page (except when we request from HTTPS to HTTP, then no Referer).

E.g. Referer: https://javascript.info/admin/secret/paths.

If we'd like other websites know only the origin part, not the URL-path, we can set the option:

We can put it to all fetch calls, maybe integrate into JavaScript library of our project that does all requests and uses fetch inside.

Its only difference compared to the default behavior is that for requests to another origin fetch sends only the origin part of the URL (e.g. https://javascript.info, without path). For requests to our origin we still get the full Referer (maybe useful for debugging purposes).

`` smart header="Referrer policy is not only forfetch " Referrer policy, described in the [specification](https://w3c.github.io/webappsec-referrer-policy/), is not just forfetch`, but more global.

In particular, it's possible to set the default policy for the whole page using the Referrer-Policy HTTP header, or per-link, with <a rel="noreferrer">.

mode

The mode option is a safe-guard that prevents occasional cross-origin requests:

This option may be useful when the URL for fetch comes from a 3rd-party, and we want a "power off switch" to limit cross-origin capabilities.

credentials

The credentials option specifies whether fetch should send cookies and HTTP-Authorization headers with the request.

cache

By default, fetch requests make use of standard HTTP-caching. That is, it respects the Expires and Cache-Control headers, sends If-Modified-Since and so on. Just like regular HTTP-requests do.

The cache options allows to ignore HTTP-cache or fine-tune its usage:

redirect

Normally, fetch transparently follows HTTP-redirects, like 301, 302 etc.

The redirect option allows to change that:

integrity

The integrity option allows to check if the response matches the known-ahead checksum.

As described in the specification, supported hash-functions are SHA-256, SHA-384, and SHA-512, there might be others depending on the browser.

For example, we're downloading a file, and we know that it's SHA-256 checksum is "abcdef" (a real checksum is longer, of course).

We can put it in the integrity option, like this:

Then fetch will calculate SHA-256 on its own and compare it with our string. In case of a mismatch, an error is triggered.

keepalive

The keepalive option indicates that the request may "outlive" the webpage that initiated it.

For example, we gather statistics on how the current visitor uses our page (mouse clicks, page fragments he views), to analyze and improve the user experience.

When the visitor leaves our page - we'd like to save the data to our server.

We can use the window.onunload event for that:

js run window.onunload = function() { fetch('/analytics', { method: 'POST', body: "statistics", *!* keepalive: true */!* }); };

Normally, when a document is unloaded, all associated network requests are aborted. But the keepalive option tells the browser to perform the request in the background, even after it leaves the page. So this option is essential for our request to succeed.

It has a few limitations:

URL objects

The built-in URL class provides a convenient interface for creating and parsing URLs.

There are no networking methods that require exactly a URL object, strings are good enough. So technically we don't have to use URL. But sometimes it can be really helpful.

Creating a URL

The syntax to create a new URL object:

For example:

These two URLs are same:

run let url1 = new URL(‘https://javascript.info/profile/admin'); let url2 = new URL(‘/profile/admin', ‘https://javascript.info');

alert(url1); // https://javascript.info/profile/admin alert(url2); // https://javascript.info/profile/admin

We can easily create a new URL based on the path relative to an existing URL:

run let url = new URL(‘https://javascript.info/profile/admin'); let newUrl = new URL(‘tester', url);

alert(newUrl); // https://javascript.info/profile/tester

The URL object immediately allows us to access its components, so it's a nice way to parse the url, e.g.:

run let url = new URL(‘https://javascript.info/url');

alert(url.protocol); // https: alert(url.host); // javascript.info alert(url.pathname); // /url

Here's the cheatsheet for URL components:

`` smart header="We can passURL objects to networking (and most other) methods instead of a string" We can use aURL object infetch orXMLHttpRequest`, almost everywhere where a URL-string is expected.

Generally, the URL object can be passed to any method instead of a string, as most methods will perform the string conversion, that turns a URL object into a string with full URL.

SearchParams "?…"

Let's say we want to create a url with given search params, for instance, https://google.com/search?query=JavaScript.

We can provide them in the URL string:

…But parameters need to be encoded if they contain spaces, non-latin letters, etc (more about that below).

So there's a URL property for that: url.searchParams, an object of type URLSearchParams.

It provides convenient methods for search parameters:

An example with parameters that contain spaces and punctuation marks:

run let url = new URL(‘https://google.com/search');

url.searchParams.set(‘q', ‘test me!'''); // added parameter with a space and !

alert(url); // https://google.com/search?q=test+me%21

url.searchParams.set(‘tbs', ‘qdr:y'); // added parameter with a colon :

// parameters are automatically encoded alert(url); // https://google.com/search?q=test+me%21&tbs=qdr%3Ay

// iterate over search parameters (decoded) for(let [name, value] of url.searchParams) { alert( ${name}=${value}); // q=test me!, then tbs=qdr:y }

Encoding

There's a standard RFC3986 that defines which characters are allowed in URLs and which are not.

Those that are not allowed, must be encoded, for instance non-latin letters and spaces - replaced with their UTF-8 codes, prefixed by %, such as %20 (a space can be encoded by +, for historical reasons, but that's an exception).

The good news is that URL objects handle all that automatically. We just supply all parameters unencoded, and then convert the URL to string:

run // using some cyrillic characters for this example

let url = new URL(‘https://ru.wikipedia.org/wiki/Тест');

url.searchParams.set(‘key', ‘ъ'); alert(url); //https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82?key=%D1%8A

                                                                                                                  
As you can see, both `Тест` in the url path and `ъ` in the parameter are encoded.
The URL became longer, because each cyrillic letter is represented with two bytes in UTF-8, so there are two `%..` entities.
### Encoding strings
In old times, before `URL` objects appeared, people used strings for URLs.
As of now, `URL` objects are often more convenient, but strings can still be used as well. In many cases using a string makes the code shorter.
If we use a string though, we need to encode/decode special characters manually.
There are built-in functions for that:
- [encodeURI](mdn:/JavaScript/Reference/Global_Objects/encodeURI) - encodes URL as a whole.
- [decodeURI](mdn:/JavaScript/Reference/Global_Objects/decodeURI) - decodes it back.
- [encodeURIComponent](mdn:/JavaScript/Reference/Global_Objects/encodeURIComponent) - encodes a URL component, such as a search parameter, or a hash, or a pathname.
- [decodeURIComponent](mdn:/JavaScript/Reference/Global_Objects/decodeURIComponent) - decodes it back.
A natural question is: "What's the difference between `encodeURIComponent` and `encodeURI`? When we should use either?"
That's easy to understand if we look at the URL, that's split into components in the picture above:

                                                                                                                

https://site.com:8080/path/page?p1=v1&p2=v2#hash

As we can see, characters such as :, ?, =, &, # are allowed in URL.

…On the other hand, if we look at a single URL component, such as a search parameter, these characters must be encoded, not to break the formatting.

So, for a whole URL we can use encodeURI:

run // using cyrillic characters in url path let url = encodeURI(‘http://site.com/привет');

alert(url); // http://site.com/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82

…While for URL parameters we should use encodeURIComponent instead:

run let music = encodeURIComponent(‘Rock&Roll');

let url = https://google.com/search?q=${music}; alert(url); // https://google.com/search?q=Rock%26Roll

Compare it with encodeURI:

run let music = encodeURI(‘Rock&Roll');

let url = https://google.com/search?q=${music}; alert(url); // https://google.com/search?q=Rock&Roll

As we can see, encodeURI does not encode &, as this is a legit character in URL as a whole.

But we should encode & inside a search parameter, otherwise, we get q=Rock&Roll - that is actually q=Rock plus some obscure parameter Roll. Not as intended.

So we should use only encodeURIComponent for each search parameter, to correctly insert it in the URL string. The safest is to encode both name and value, unless we're absolutely sure that it has only allowed characters.

smart header="Encoding difference compared toURL " Classes [URL](https://url.spec.whatwg.org/#url-class) and [URLSearchParams](https://url.spec.whatwg.org/#interface-urlsearchparams) are based on the latest URI specification: [RFC3986](https://tools.ietf.org/html/rfc3986), whileencode*` functions are based on the obsolete version RFC2396.

There are a few differences, e.g. IPv6 addresses are encoded differently:

run // valid url with IPv6 address let url = ‘http://[2607:f8b0:4005:802::1007]/''';

alert(encodeURI(url)); // http://%5B2607:f8b0:4005:802::1007%5D/ alert(new URL(url)); // http://[2607:f8b0:4005:802::1007]/

                                                                                                                  
As we can see, `encodeURI` replaced square brackets `[...]`, that's not correct, the reason is: IPv6 urls did not exist at the time of RFC2396 (August 1998).
Such cases are rare, `encode*` functions work well most of the time.
                                                                                                                

XMLHttpRequest

XMLHttpRequest is a built-in browser object that allows to make HTTP requests in JavaScript.

Despite of having the word "XML" in its name, it can operate on any data, not only in XML format. We can upload/download files, track progress and much more.

Right now, there's another, more modern method fetch, that somewhat deprecates XMLHttpRequest.

In modern web-development XMLHttpRequest is used for three reasons:

  1. Historical reasons: we need to support existing scripts with XMLHttpRequest.
  2. We need to support old browsers, and don't want polyfills (e.g. to keep scripts tiny).
  3. We need something that fetch can't do yet, e.g. to track upload progress.

Does that sound familiar? If yes, then all right, go on with XMLHttpRequest. Otherwise, please head on to info:fetch.

The basics

XMLHttpRequest has two modes of operation: synchronous and asynchronous.

Let's see the asynchronous first, as it's used in the majority of cases.

To do the request, we need 3 steps:

  1. Create XMLHttpRequest: js let xhr = new XMLHttpRequest(); The constructor has no arguments.

  2. Initialize it, usually right after new XMLHttpRequest: js xhr.open(method, URL, [async, user, password])

    This method specifies the main parameters of the request:

    • method - HTTP-method. Usually "GET" or "POST".
    • URL - the URL to request, a string, can be URL object.
    • async - if explicitly set to false, then the request is synchronous, we'll cover that a bit later.
    • user, password - login and password for basic HTTP auth (if required).

    Please note that open call, contrary to its name, does not open the connection. It only configures the request, but the network activity only starts with the call of send.

  3. Send it out.

    This method opens the connection and sends the request to server. The optional body parameter contains the request body.

    Some request methods like GET do not have a body. And some of them like POST use body to send the data to the server. We'll see examples of that later.

  4. Listen to xhr events for response.

    These three events are the most widely used:
    • load - when the request is complete (even if HTTP status is like 400 or 500), and the response is fully downloaded.
    • error - when the request couldn't be made, e.g. network down or invalid URL.
    • progress - triggers periodically while the response is being downloaded, reports how much has been downloaded.

Here's a full example. The code below loads the URL at /article/xmlhttprequest/example/load from the server and prints the progress:

run // 1. Create a new XMLHttpRequest object let xhr = new XMLHttpRequest();

// 2. Configure it: GET-request for the URL /article/…/load xhr.open(‘GET', ‘/article/xmlhttprequest/example/load');

// 3. Send the request over the network xhr.send();

// 4. This will be called after the response is received xhr.onload = function() { if (xhr.status != 200) { // analyze HTTP status of the response alert( Error ${xhr.status}: ${xhr.statusText}); // e.g. 404: Not Found } else { // show the result alert( Done, got ${xhr.response.length} bytes); // response is the server response } };

xhr.onprogress = function(event) { if (event.lengthComputable) { alert( Received ${event.loaded} of ${event.total} bytes); } else { alert( Received ${event.loaded} bytes); // no Content-Length }

};

xhr.onerror = function() { alert("Request failed"); };

Once the server has responded, we can receive the result in the following xhr properties:

status
HTTP status code (a number): 200, 404, 403 and so on, can be 0 in case of a non-HTTP failure.
statusText
HTTP status message (a string): usually OK for 200, Not Found for 404, Forbidden for 403 and so on.
response (old scripts may use responseText)
The server response body.

We can also specify a timeout using the corresponding property:

If the request does not succeed within the given time, it gets canceled and timeout event triggers.

smart header="URL search parameters" To add parameters to URL, like?name=value`, and ensure the proper encoding, we can use URL object:

Response Type

We can use xhr.responseType property to set the response format:

For example, let's get the response as JSON:

run let xhr = new XMLHttpRequest();

xhr.open(‘GET', ‘/article/xmlhttprequest/example/json');

! xhr.responseType = ‘json'; /!

xhr.send();

// the response is {"message": "Hello, world!"} xhr.onload = function() { let responseObj = xhr.response; alert(responseObj.message); // Hello, world! };

                                                                                                                  In the old scripts you may also find `xhr.responseText` and even `xhr.responseXML` properties.
They exist for historical reasons, to get either a string or XML document. Nowadays, we should set the format in `xhr.responseType` and get `xhr.response` as demonstrated above.
                                                                                                                

Ready states

XMLHttpRequest changes between states as it progresses. The current state is accessible as xhr.readyState.

All states, as in the specification:

An XMLHttpRequest object travels them in the order 0 -> 1 -> 2 -> 3 -> … -> 3 -> 4. State 3 repeats every time a data packet is received over the network.

We can track them using readystatechange event:

You can find readystatechange listeners in really old code, it's there for historical reasons, as there was a time when there were no load and other events. Nowadays, load/error/progress handlers deprecate it.

Aborting request

We can terminate the request at any time. The call to xhr.abort() does that:

That triggers abort event, and xhr.status becomes 0.

Synchronous requests

If in the open method the third parameter async is set to false, the request is made synchronously.

In other words, JavaScript execution pauses at send() and resumes when the response is received. Somewhat like alert or prompt commands.

Here's the rewritten example, the 3rd parameter of open is false:

It might look good, but synchronous calls are used rarely, because they block in-page JavaScript till the loading is complete. In some browsers it becomes impossible to scroll. If a synchronous call takes too much time, the browser may suggest to close the "hanging" webpage.

Many advanced capabilities of XMLHttpRequest, like requesting from another domain or specifying a timeout, are unavailable for synchronous requests. Also, as you can see, no progress indication.

Because of all that, synchronous requests are used very sparingly, almost never. We won't talk about them any more.

HTTP-headers

XMLHttpRequest allows both to send custom headers and read headers from the response.

There are 3 methods for HTTP-headers:

setRequestHeader(name, value)

Sets the request header with the given name and value.

For instance:

`` warn header="Headers limitations" Several headers are managed exclusively by the browser, e.g.Referer andHost`. The full list is in the specification.

XMLHttpRequest is not allowed to change them, for the sake of user safety and correctness of the request.

warn header="Can't remove a header" Another peculiarity ofXMLHttpRequest is that one can't undosetRequestHeader`.

Once the header is set, it's set. Additional calls add information to the header, don't overwrite it.

For instance:

getResponseHeader(name)

Gets the response header with the given name (except Set-Cookie and Set-Cookie2).

For instance:

getAllResponseHeaders()

Returns all response headers, except Set-Cookie and Set-Cookie2.

Headers are returned as a single line, e.g.:

                                                                                                                      Cache-Control: max-age=31536000
Content-Length: 4260
Content-Type: image/png
Date: Sat, 08 Sep 2012 16:53:16 GMT
                                                                                                                    

The line break between headers is always "\r\n" (doesn't depend on OS), so we can easily split it into individual headers. The separator between the name and the value is always a colon followed by a space ": ". That's fixed in the specification.

So, if we want to get an object with name/value pairs, we need to throw in a bit JS.

Like this (assuming that if two headers have the same name, then the latter one overwrites the former one):

POST, FormData

To make a POST request, we can use the built-in FormData object.

The syntax:

We create it, optionally fill from a form, append more fields if needed, and then:

  1. xhr.open('POST', ...) - use POST method.
  2. xhr.send(formData) to submit the form to the server.

For instance:

run refresh

The form is sent with multipart/form-data encoding.

Or, if we like JSON more, then JSON.stringify and send as a string.

Just don't forget to set the header Content-Type: application/json, many server-side frameworks automatically decode JSON with it:

The .send(body) method is pretty omnivore. It can send almost any body, including Blob and BufferSource objects.

Upload progress

The progress event triggers only on the downloading stage.

That is: if we POST something, XMLHttpRequest first uploads our data (the request body), then downloads the response.

If we're uploading something big, then we're surely more interested in tracking the upload progress. But xhr.onprogress doesn't help here.

There's another object, without methods, exclusively to track upload events: xhr.upload.

It generates events, similar to xhr, but xhr.upload triggers them solely on uploading:

Example of handlers:

Here's a real-life example: file upload with progress indication:

run

Cross-origin requests

XMLHttpRequest can make cross-origin requests, using the same CORS policy as fetch.

Just like fetch, it doesn't send cookies and HTTP-authorization to another origin by default. To enable them, set xhr.withCredentials to true:

See the chapter info:fetch-crossorigin for details about cross-origin headers.

Summary

Typical code of the GET-request with XMLHttpRequest:

There are actually more events, the modern specification lists them (in the lifecycle order):

The error, abort, timeout, and load events are mutually exclusive. Only one of them may happen.

The most used events are load completion ( load), load failure ( error), or we can use a single loadend handler and check the properties of the request object xhr to see what happened.

We've already seen another event: readystatechange. Historically, it appeared long ago, before the specification settled. Nowadays, there's no need to use it, we can replace it with newer events, but it can often be found in older scripts.

If we need to track uploading specifically, then we should listen to same events on xhr.upload object.

Conditional branching: if, ‘?'''

Sometimes, we need to perform different actions based on different conditions.

To do that, we can use the if statement and the conditional operator ?, that's also called a "question mark" operator.

The "if" statement

The if(...) statement evaluates a condition in parentheses and, if the result is true, executes a block of code.

For example:

run let year = prompt(‘In which year was ECMAScript-2015 specification published?''', '');

! if (year == 2015) alert( ‘You are right!''' ); /!

In the example above, the condition is a simple equality check ( year == 2015), but it can be much more complex.

If we want to execute more than one statement, we have to wrap our code block inside curly braces:

We recommend wrapping your code block with curly braces {} every time you use an if statement, even if there is only one statement to execute. Doing so improves readability.

Boolean conversion

The if (…) statement evaluates the expression in its parentheses and converts the result to a boolean.

Let's recall the conversion rules from the chapter info:type-conversions:

So, the code under this condition would never execute:

…and inside this condition - it always will:

We can also pass a pre-evaluated boolean value to if, like this:

The "else" clause

The if statement may contain an optional "else" block. It executes when the condition is falsy.

For example: run let year = prompt(‘In which year was the ECMAScript-2015 specification published?''', '');

if (year == 2015) { alert( ‘You guessed it right!''' ); } else { alert( ‘How can you be so wrong?''' ); // any value except 2015 }

Several conditions: "else if"

Sometimes, we'd like to test several variants of a condition. The else if clause lets us do that.

For example:

run let year = prompt(‘In which year was the ECMAScript-2015 specification published?''', '');

if (year < 2015) { alert( ‘Too early…' ); } else if (year > 2015) { alert( ‘Too late' ); } else { alert( ‘Exactly!''' ); }

In the code above, JavaScript first checks year < 2015. If that is falsy, it goes to the next condition year > 2015. If that is also falsy, it shows the last alert.

There can be more else if blocks. The final else is optional.

Conditional operator ‘?'''

Sometimes, we need to assign a variable depending on a condition.

For instance:

run no-beautify let accessAllowed; let age = prompt(‘How old are you?''', '');

! if (age > 18) { accessAllowed = true; } else { accessAllowed = false; } /!

alert(accessAllowed);

The so-called "conditional" or "question mark" operator lets us do that in a shorter and simpler way.

The operator is represented by a question mark ?. Sometimes it's called "ternary", because the operator has three operands. It is actually the one and only operator in JavaScript which has that many.

The syntax is:

The condition is evaluated: if it's truthy then value1 is returned, otherwise - value2.

For example:

Technically, we can omit the parentheses around age > 18. The question mark operator has a low precedence, so it executes after the comparison >.

This example will do the same thing as the previous one:

But parentheses make the code more readable, so we recommend using them.

                                                                                                                        In the example above, you can avoid using the question mark operator because the comparison itself returns `true/false`:
// the same
let accessAllowed = age > 18;

                                                                                                                      

Multiple ‘?'''

A sequence of question mark operators ? can return a value that depends on more than one condition.

For instance: run let age = prompt(‘age?''', 18);

let message = (age < 3) ? ‘Hi, baby!''' : (age < 18) ? ‘Hello!''' : (age < 100) ? ‘Greetings!''' : ‘What an unusual age!''';

alert( message );

It may be difficult at first to grasp what's going on. But after a closer look, we can see that it's just an ordinary sequence of tests:

  1. The first question mark checks whether age < 3.
  2. If true - it returns 'Hi, baby!'. Otherwise, it continues to the expression after the colon ‘":"', checking age < 18.
  3. If that's true - it returns 'Hello!'. Otherwise, it continues to the expression after the next colon ‘":"', checking age < 100.
  4. If that's true - it returns 'Greetings!'. Otherwise, it continues to the expression after the last colon ‘":"', returning 'What an unusual age!'.

Here's how this looks using if..else:

Non-traditional use of ‘?'''

Sometimes the question mark ? is used as a replacement for if:

run no-beautify let company = prompt(‘Which company created JavaScript?''', '');

! (company == ‘Netscape') ? alert(‘Right!''') : alert(‘Wrong.'''); /!

Depending on the condition company == 'Netscape', either the first or the second expression after the ? gets executed and shows an alert.

We don't assign a result to a variable here. Instead, we execute different code depending on the condition.

It's not recommended to use the question mark operator in this way.

The notation is shorter than the equivalent if statement, which appeals to some programmers. But it is less readable.

Here is the same code using if for comparison:

run no-beautify let company = prompt(‘Which company created JavaScript?''', '');

! if (company == ‘Netscape') { alert(‘Right!'''); } else { alert(‘Wrong.'''); } /!

Our eyes scan the code vertically. Code blocks which span several lines are easier to understand than a long, horizontal instruction set.

The purpose of the question mark operator ? is to return one value or another depending on its condition. Please use it for exactly that. Use if when you need to execute different branches of code.

Resumable file upload

With fetch method it's fairly easy to upload a file.

How to resume the upload after lost connection? There's no built-in option for that, but we have the pieces to implement it.

Resumable uploads should come with upload progress indication, as we expect big files (if we may need to resume). So, as fetch doesn't allow to track upload progress, we'll use XMLHttpRequest.

Not-so-useful progress event

To resume upload, we need to know how much was uploaded till the connection was lost.

There's xhr.upload.onprogress to track upload progress.

Unfortunately, it won't help us to resume the upload here, as it triggers when the data is sent, but was it received by the server? The browser doesn't know.

Maybe it was buffered by a local network proxy, or maybe the remote server process just died and couldn't process them, or it was just lost in the middle and didn't reach the receiver.

That's why this event is only useful to show a nice progress bar.

To resume upload, we need to know exactly the number of bytes received by the server. And only the server can tell that, so we'll make an additional request.

Algorithm

  1. First, create a file id, to uniquely identify the file we're going to upload: js let fileId = file.name + '-' + file.size + '-' + file.lastModified; That's needed for resume upload, to tell the server what we're resuming.

    If the name or the size or the last modification date changes, then there'll be another fileId.

  2. Send a request to the server, asking how many bytes it already has, like this: let response = await fetch(‘status', { headers: { ‘X-File-Id': fileId } });

    // The server has that many bytes let startByte = +await response.text();

    This assumes that the server tracks file uploads by X-File-Id header. Should be implemented at server-side.

    If the file doesn't yet exist at the server, then the server response should be 0

  3. Then, we can use Blob method slice to send the file from startByte: xhr.open("POST", "upload", true);

    // File id, so that the server knows which file we upload xhr.setRequestHeader(‘X-File-Id', fileId);

    // The byte we're resuming from, so the server knows we're resuming xhr.setRequestHeader(‘X-Start-Byte', startByte);

    xhr.upload.onprogress = (e) => { console.log( Uploaded ${startByte + e.loaded} of ${startByte + e.total}); };

    // file can be from input.files[0] or another source xhr.send(file.slice(startByte));

    Here we send the server both file id as X-File-Id, so it knows which file we're uploading, and the starting byte as X-Start-Byte, so it knows we're not uploading it initially, but resuming.

    The server should check its records, and if there was an upload of that file, and the current uploaded size is exactly X-Start-Byte, then append the data to it.

Here's the demo with both client and server code, written on Node.js.

It works only partially on this site, as Node.js is behind another server named Nginx, that buffers uploads, passing them to Node.js when fully complete.

But you can download it and run locally for the full demonstration:

[codetabs src="upload-resume" height=200]

As we can see, modern networking methods are close to file managers in their capabilities - control over headers, progress indicator, sending file parts, etc.

We can implement resumable upload and much more.

Long polling

Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Side Events.

Being very easy to implement, it's also good enough in a lot of cases.

Regular Polling

The simplest way to get new information from the server is periodic polling. That is, regular requests to the server: "Hello, I'm here, do you have any information for me?". For example, once every 10 seconds.

In response, the server first takes a notice to itself that the client is online, and second - sends a packet of messages it got till that moment.

That works, but there are downsides: 1. Messages are passed with a delay up to 10 seconds (between requests). 2. Even if there are no messages, the server is bombed with requests every 10 seconds, even if the user switched somewhere else or is asleep. That's quite a load to handle, speaking performance-wise.

So, if we're talking about a very small service, the approach may be viable, but generally, it needs an improvement.

Long polling

So-called "long polling" is a much better way to poll the server.

It's also very easy to implement, and delivers messages without delays.

The flow:

  1. A request is sent to the server.
  2. The server doesn't close the connection until it has a message to send.
  3. When a message appears - the server responds to the request with it.
  4. The browser makes a new request immediately.

The situation when the browser sent a request and has a pending connection with the server, is standard for this method. Only when a message is delivered, the connection is reestablished.

If the connection is lost, because of, say, a network error, the browser immediately sends a new request.

A sketch of client-side subscribe function that makes long requests:

As you can see, subscribe function makes a fetch, then waits for the response, handles it and calls itself again.

warn header="Server should be ok with many pending connections" The server architecture must be able to work with many pending connections.

Certain server architectures run one process per connection, resulting in there being as many processes as there are connections, while each process consumes quite a bit of memory. So, too many connections will just consume it all.

That's often the case for backends written in languages like PHP and Ruby.

Servers written using Node.js usually don't have such problems.

That said, it isn't a programming language issue. Most modern languages, including PHP and Ruby allow to implement a proper backend. Just please make sure that your server architecture works fine with many simultaneous connections.

Demo: a chat

Here's a demo chat, you can also download it and run locally (if you're familiar with Node.js and can install modules):

[codetabs src="longpoll" height=500]

Browser code is in browser.js.

Area of usage

Long polling works great in situations when messages are rare.

If messages come very often, then the chart of requesting-receiving messages, painted above, becomes saw-like.

Every message is a separate request, supplied with headers, authentication overhead, and so on.

So, in this case, another method is preferred, such as Websocket or Server Sent Events.

WebSocket

The WebSocket protocol, described in the specification RFC 6455 provides a way to exchange data between browser and server via a persistent connection. The data can be passed in both directions as "packets", without breaking the connection and additional HTTP-requests.

WebSocket is especially great for services that require continuous data exchange, e.g. online games, real-time trading systems and so on.

A simple example

To open a websocket connection, we need to create new WebSocket using the special protocol ws in the url:

There's also encrypted wss:// protocol. It's like HTTPS for websockets.

`` smart header="Always preferwss:// " Thewss://` protocol is not only encrypted, but also more reliable.

That's because ws:// data is not encrypted, visible for any intermediary. Old proxy servers do not know about WebSocket, they may see "strange" headers and abort the connection.

On the other hand, wss:// is WebSocket over TLS, (same as HTTPS is HTTP over TLS), the transport security layer encrypts the data at sender and decrypts at the receiver. So data packets are passed encrypted through proxies. They can't see what's inside and let them through.

Once the socket is created, we should listen to events on it. There are totally 4 events: - open - connection established, - message - data received, - error - websocket error, - close - connection closed.

…And if we'd like to send something, then socket.send(data) will do that.

Here's an example:

run let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");

socket.onopen = function(e) { alert("[open] Connection established"); alert("Sending to server"); socket.send("My name is John"); };

socket.onmessage = function(event) { alert( [message] Data received from server: ${event.data}); };

socket.onclose = function(event) { if (event.wasClean) {
alert( [close] Connection closed cleanly, code=${event.code} reason=${event.reason}); } else { // e.g. server process killed or network down // event.code is usually 1006 in this case alert(‘[close] Connection died'); } };

socket.onerror = function(error) { alert( [error] ${error.message}); };

                                                                                                                        
For demo purposes, there's a small server [server.js](demo/server.js) written in Node.js, for the example above, running. It responds with "Hello from server, John", then waits 5 seconds and closes the connection.
So you'll see events `open` -> `message` -> `close`.
That's actually it, we can talk WebSocket already. Quite simple, isn't it?
Now let's talk more in-depth.
## Opening a websocket
When `new WebSocket(url)` is created, it starts connecting immediately.
During the connection the browser (using headers) asks the server: "Do you support Websocket?" And if the server replies "yes", then the talk continues in WebSocket protocol, which is not HTTP at all.
![](websocket-handshake.svg)
Here's an example of browser headers for request made by `new WebSocket("wss://javascript.info/chat")`.

                                                                                                                      

GET /chat Host: javascript.info Origin: https://javascript.info Connection: Upgrade Upgrade: websocket Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13

smart header="WebSocket handshake can't be emulated" We can't use `XMLHttpRequest` or `fetch` to make this kind of HTTP-request, because JavaScript is not allowed to set these headers.

If the server agrees to switch to WebSocket, it should send code 101 response:

                                                                                                                        101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
                                                                                                                      

Here Sec-WebSocket-Accept is Sec-WebSocket-Key, recoded using a special algorithm. The browser uses it to make sure that the response corresponds to the request.

Afterwards, the data is transfered using WebSocket protocol, we'll see its structure ("frames") soon. And that's not HTTP at all.

Extensions and subprotocols

There may be additional headers Sec-WebSocket-Extensions and Sec-WebSocket-Protocol that describe extensions and subprotocols.

For instance:

The server should respond with a list of protocols and extensions that it agrees to use.

For example, the request:

                                                                                                                        GET /chat
Host: javascript.info
Upgrade: websocket
Connection: Upgrade
Origin: https://javascript.info
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
*!*
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap, wamp
*/!*
                                                                                                                      

Response:

                                                                                                                        101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
*!*
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap
*/!*
                                                                                                                      

Here the server responds that it supports the extension "deflate-frame", and only SOAP of the requested subprotocols.

Data transfer

WebSocket communication consists of "frames" - data fragments, that can be sent from either side, and can be of several kinds:

In the browser, we directly work only with text or binary frames.

WebSocket .send() method can send either text or binary data.

A call socket.send(body) allows body in string or a binary format, including Blob, ArrayBuffer, etc. No settings required: just send it out in any format.

When we receive the data, text always comes as string. And for binary data, we can choose between Blob and ArrayBuffer formats.

That's set by socket.binaryType property, it's "blob" by default, so binary data comes as Blob objects.

Blob is a high-level binary object, it directly integrates with <a>, <img> and other tags, so that's a sane default. But for binary processing, to access individual data bytes, we can change it to "arraybuffer":

Rate limiting

Imagine, our app is generating a lot of data to send. But the user has a slow network connection, maybe on a mobile internet, outside of a city.

We can call socket.send(data) again and again. But the data will be buffered (stored) in memory and sent out only as fast as network speed allows.

The socket.bufferedAmount property stores how many bytes remain buffered at this moment, waiting to be sent over the network.

We can examine it to see whether the socket is actually available for transmission.

Connection close

Normally, when a party wants to close the connection (both browser and server have equal rights), they send a "connection close frame" with a numeric code and a textual reason.

The method for that is:

Then the other party in close event handler gets the code and the reason, e.g.:

Most common code values:

There are other codes like:

The full list can be found in RFC6455, §7.4.1.

WebSocket codes are somewhat like HTTP codes, but different. In particular, any codes less than 1000 are reserved, there'll be an error if we try to set such a code.

Connection state

To get connection state, additionally there's socket.readyState property with values:

Chat example

Let's review a chat example using browser WebSocket API and Node.js WebSocket module https://github.com/websockets/ws. We'll pay the main attention to the client side, but the server is also simple.

HTML: we need a <form> to send messages and a <div> for incoming messages:

From JavaScript we want three things: 1. Open the connection. 2. On form submission - socket.send(message) for the message. 3. On incoming message - append it to div#messages.

Here's the code:

Server-side code is a little bit beyond our scope. Here we'll use Node.js, but you don't have to. Other platforms also have their means to work with WebSocket.

The server-side algorithm will be:

  1. Create clients = new Set() - a set of sockets.
  2. For each accepted websocket, add it to the set clients.add(socket) and setup message event listener to get its messages.
  3. When a message received: iterate over clients and send it to everyone.
  4. When a connection is closed: clients.delete(socket).

Here's the working example:

[iframe src="chat" height="100" zip]

You can also download it (upper-right button in the iframe) and run locally. Just don't forget to install Node.js and npm install ws before running.

Summary

WebSocket is a modern way to have persistent browser-server connections.

The API is simple.

Methods: - socket.send(data), - socket.close([code], [reason]).

Events: - open, - message, - error, - close.

WebSocket by itself does not include reconnection, authentication and many other high-level mechanisms. So there are client/server libraries for that, and it's also possible to implement these capabilities manually.

Sometimes, to integrate WebSocket into existing project, people run WebSocket server in parallel with the main HTTP-server, and they share a single database. Requests to WebSocket use wss://ws.site.com, a subdomain that leads to WebSocket server, while https://site.com goes to the main HTTP-server.

Surely, other ways of integration are also possible.

Server Sent Events

The Server-Sent Events specification describes a built-in class EventSource, that keeps connection with the server and allows to receive events from it.

Similar to WebSocket, the connection is persistent.

But there are several important differences:

WebSocket EventSource
Bi-directional: both client and server can exchange messages One-directional: only server sends data
Binary and text data Only text
WebSocket protocol Regular HTTP

EventSource is a less-powerful way of communicating with the server than WebSocket.

Why should one ever use it?

The main reason: it's simpler. In many applications, the power of WebSocket is a little bit too much.

We need to receive a stream of data from server: maybe chat messages or market prices, or whatever. That's what EventSource is good at. Also it supports auto-reconnect, something we need to implement manually with WebSocket. Besides, it's a plain old HTTP, not a new protocol.

Getting messages

To start receiving messages, we just need to create new EventSource(url).

The browser will connect to url and keep the connection open, waiting for events.

The server should respond with status 200 and the header Content-Type: text/event-stream, then keep the connection and write messages into it in the special format, like this:

                                                                                                                        data: Message 1
data: Message 2
data: Message 3
data: of two lines
                                                                                                                      

In practice, complex messages are usually sent JSON-encoded. Line-breaks are encoded as \n within them, so multiline data: messages are not necessary.

For instance:

…So we can assume that one data: holds exactly one message.

For each such message, the message event is generated:

Cross-origin requests

EventSource supports cross-origin requests, like fetch and any other networking methods. We can use any URL:

The remote server will get the Origin header and must respond with Access-Control-Allow-Origin to proceed.

To pass credentials, we should set the additional option withCredentials, like this:

Please see the chapter info:fetch-crossorigin for more details about cross-origin headers.

Reconnection

Upon creation, new EventSource connects to the server, and if the connection is broken - reconnects.

That's very convenient, as we don't have to care about it.

There's a small delay between reconnections, a few seconds by default.

The server can set the recommended delay using retry: in response (in milliseconds):

The retry: may come both together with some data, or as a standalone message.

The browser should wait that many milliseconds before reconnecting. Or longer, e.g. if the browser knows (from OS) that there's no network connection at the moment, it may wait until the connection appears, and then retry.

Also, there will be no reconnection if the response has an incorrect Content-Type or its HTTP status differs from 301, 307, 200 and 204. In such cases the "error" event will be emitted, and the browser won't reconnect.

                                                                                                                        When a connection is finally closed, there's no way to "reopen" it. If we'd like to connect again, just create a new `EventSource`.
                                                                                                                      

Message id

When a connection breaks due to network problems, either side can't be sure which messages were received, and which weren't.

To correctly resume the connection, each message should have an id field, like this:

                                                                                                                        data: Message 1
id: 1
data: Message 2
id: 2
data: Message 3
data: of two lines
id: 3
                                                                                                                      

When a message with id: is received, the browser:

smart header="Put `id:` after `data:`" Please note: the `id` is appended below message `data` by the server, to ensure that `lastEventId` is updated after the message is received.

Connection status: readyState

The EventSource object has readyState property, that has one of three values:

js no-beautify EventSource.CONNECTING = 0; // connecting or reconnecting EventSource.OPEN = 1; // connected EventSource.CLOSED = 2; // connection closed

When an object is created, or the connection is down, it's always EventSource.CONNECTING (equals 0).

We can query this property to know the state of EventSource.

Event types

By default EventSource object generates three events:

The server may specify another type of event with event: ... at the event start.

For example:

                                                                                                                        event: join
data: Bob
data: Hello
event: leave
data: Bob
                                                                                                                      

To handle custom events, we must use addEventListener, not onmessage:

Full example

Here's the server that sends messages with 1, 2, 3, then bye and breaks the connection.

Then the browser automatically reconnects.

[codetabs src="eventsource"]

Summary

EventSource object automatically establishes a persistent connection and allows the server to send messages over it.

It offers: - Automatic reconnect, with tunable retry timeout. - Message ids to resume events, the last received identifier is sent in Last-Event-ID header upon reconnection. - The current state is in the readyState property.

That makes EventSource a viable alternative to WebSocket, as the latter is more low-level and lacks such built-in features (though they can be implemented).

In many real-life applications, the power of EventSource is just enough.

Supported in all modern browsers (not IE).

The syntax is:

The second argument has only one possible option: { withCredentials: true }, it allows sending cross-origin credentials.

Overall cross-origin security is same as for fetch and other network methods.

Properties of an EventSource object

readyState
The current connection state: either EventSource.CONNECTING (=0), EventSource.OPEN (=1) or EventSource.CLOSED (=2).
lastEventId
The last received id. Upon reconnection the browser sends it in the header Last-Event-ID.

Methods

close()
Closes the connection.

Events

message
Message received, the data is in event.data.
open
The connection is established.
error
In case of an error, including both lost connection (will auto-reconnect) and fatal errors. We can check readyState to see if the reconnection is being attempted.

The server may set a custom event name in event:. Such events should be handled using addEventListener, not on<event>.

Server response format

The server sends messages, delimited by \n\n.

A message may have following fields:

A message may include one or more fields in any order, but id: usually goes the last.

Cookies, document.cookie

Cookies are small strings of data that are stored directly in the browser. They are a part of the HTTP protocol, defined by the RFC 6265 specification.

Cookies are usually set by a web-server using the response Set-Cookie HTTP-header. Then, the browser automatically adds them to (almost) every request to the same domain using the Cookie HTTP-header.

One of the most widespread use cases is authentication:

  1. Upon sign in, the server uses the Set-Cookie HTTP-header in the response to set a cookie with a unique "session identifier".
  2. Next time when the request is sent to the same domain, the browser sends the cookie over the net using the Cookie HTTP-header.
  3. So the server knows who made the request.

We can also access cookies from the browser, using document.cookie property.

There are many tricky things about cookies and their options. In this chapter we'll cover them in detail.

Reading from document.cookie

                                                                                                                        Does your browser store any cookies from this site? Let's see:
                                                                                                                      
                                                                                                                        Assuming you're on a website, it's possible to see the cookies from it, like this:
                                                                                                                      

js run // At javascript.info, we use Google Analytics for statistics, // so there should be some cookies alert( document.cookie ); // cookie1=value1; cookie2=value2;...

The value of document.cookie consists of name=value pairs, delimited by ;. Each one is a separate cookie.

To find a particular cookie, we can split document.cookie by ;, and then find the right name. We can use either a regular expression or array functions to do that.

We leave it as an exercise for the reader. Also, at the end of the chapter you'll find helper functions to manipulate cookies.

Writing to document.cookie

We can write to document.cookie. But it's not a data property, it's an accessor (getter/setter). An assignment to it is treated specially.

A write operation to document.cookie updates only cookies mentioned in it, but doesn't touch other cookies.

For instance, this call sets a cookie with the name user and value John:

js run document.cookie = "user=John"; // update only cookie named 'user' alert(document.cookie); // show all cookies

If you run it, then probably you'll see multiple cookies. That's because the document.cookie= operation does not overwrite all cookies. It only sets the mentioned cookie user.

Technically, name and value can have any characters. To keep the valid formatting, they should be escaped using a built-in encodeURIComponent function:

run // special characters (spaces), need encoding let name = "my name"; let value = "John Smith"

// encodes the cookie as my%20name=John%20Smith document.cookie = encodeURIComponent(name) + ‘=''' + encodeURIComponent(value);

alert(document.cookie); // …; my%20name=John%20Smith

warn header="Limitations" There are few limitations: - The `name=value` pair, after `encodeURIComponent`, should not exceed 4KB. So we can't store anything huge in a cookie. - The total number of cookies per domain is limited to around 20+, the exact limit depends on the browser.

Cookies have several options, many of them are important and should be set.

The options are listed after key=value, delimited by ;, like this:

js run document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"

path

The url path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path.

If a cookie is set with path=/admin, it's visible at pages /admin and /admin/something, but not at /home or /adminpage.

Usually, we should set path to the root: path=/ to make the cookie accessible from all website pages.

domain

A domain defines where the cookie is accessible. In practice though, there are limitations. We can't set any domain.

By default, a cookie is accessible only at the domain that set it. So, if the cookie was set by site.com, we won't get it at other.com.

…But what's more tricky, we also won't get the cookie at a subdomain forum.site.com!

There's no way to let a cookie be accessible from another 2nd-level domain, so other.com will never receive a cookie set at site.com.

It's a safety restriction, to allow us to store sensitive data in cookies, that should be available only on one site.

…But if we'd like to allow subdomains like forum.site.com to get a cookie, that's possible. When setting a cookie at site.com, we should explicitly set the domain option to the root domain: domain=site.com:

For historical reasons, domain=.site.com (a dot before site.com) also works the same way, allowing access to the cookie from subdomains. That's an old notation and should be used if we need to support very old browsers.

So, the domain option allows to make a cookie accessible at subdomains.

expires, max-age

By default, if a cookie doesn't have one of these options, it disappears when the browser is closed. Such cookies are called "session cookies"

To let cookies survive a browser close, we can set either the expires or max-age option.

The cookie expiration date defines the time, when the browser will automatically delete it.

The date must be exactly in this format, in the GMT timezone. We can use date.toUTCString to get it. For instance, we can set the cookie to expire in 1 day:

If we set expires to a date in the past, the cookie is deleted.

Is an alternative to expires and specifies the cookie's expiration in seconds from the current moment.

If set to zero or a negative value, the cookie is deleted:

secure

The cookie should be transferred only over HTTPS.

By default, if we set a cookie at http://site.com, then it also appears at https://site.com and vice versa.

That is, cookies are domain-based, they do not distinguish between the protocols.

With this option, if a cookie is set by https://site.com, then it doesn't appear when the same site is accessed by HTTP, as http://site.com. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the secure flag is the right thing.

samesite

That's another security attribute samesite. It's designed to protect from so-called XSRF (cross-site request forgery) attacks.

To understand how it works and when it's useful, let's take a look at XSRF attacks.

XSRF attack

Imagine, you are logged into the site bank.com. That is: you have an authentication cookie from that site. Your browser sends it to bank.com with every request, so that it recognizes you and performs all sensitive financial operations.

Now, while browsing the web in another window, you accidentally come to another site evil.com. That site has JavaScript code that submits a form <form action="https://bank.com/pay"> to bank.com with fields that initiate a transaction to the hacker's account.

The browser sends cookies every time you visit the site bank.com, even if the form was submitted from evil.com. So the bank recognizes you and actually performs the payment.

That's a so-called "Cross-Site Request Forgery" (in short, XSRF) attack.

Real banks are protected from it of course. All forms generated by bank.com have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site bank.com checks for such token in every form it receives.

Such a protection takes time to implement though. We need to ensure that every form has the required token field, and we must also check all requests.

The cookie samesite option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens".

It has two possible values:

A cookie with samesite=strict is never sent if the user comes from outside the same site.

In other words, whether a user follows a link from their mail or submits a form from evil.com, or does any operation that originates from another domain, the cookie is not sent.

If authentication cookies have the samesite option, then a XSRF attack has no chances to succeed, because a submission from evil.com comes without cookies. So bank.com will not recognize the user and will not proceed with the payment.

The protection is quite reliable. Only operations that come from bank.com will send the samesite cookie, e.g. a form submission from another page at bank.com.

Although, there's a small inconvenience.

When a user follows a legitimate link to bank.com, like from their own notes, they'll be surprised that bank.com does not recognize them. Indeed, samesite=strict cookies are not sent in that case.

We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with samesite=strict. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent.

A more relaxed approach that also protects from XSRF and doesn't break the user experience.

Lax mode, just like strict, forbids the browser to send cookies when coming from outside the site, but adds an exception.

A samesite=lax cookie is sent if both of these conditions are true: 1. The HTTP method is "safe" (e.g. GET, but not POST).

                                                                                                                        The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231). Basically, these are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method.
                                                                                                                      
  1. The operation performs a top-level navigation (changes URL in the browser address bar).

    That's usually true, but if the navigation is performed in an <iframe>, then it's not top-level. Also, JavaScript methods for network requests do not perform any navigation, hence they don't fit.

So, what samesite=lax does, is to basically allow the most common "go to URL" operation to have cookies. E.g. opening a website link from notes that satisfy these conditions.

But anything more complicated, like a network request from another site or a form submission, loses cookies.

If that's fine for you, then adding samesite=lax will probably not break the user experience and add protection.

Overall, samesite is a great option, but it has an important drawback: - samesite is ignored (not supported) by old browsers, year 2017 or so.

So if we solely rely on samesite to provide protection, then old browsers will be vulnerable.

But we surely can use samesite together with other protection measures, like xsrf tokens, to add an additional layer of defence and then, in the future, when old browsers die out, we'll probably be able to drop xsrf tokens.

httpOnly

This option has nothing to do with JavaScript, but we have to mention it for completeness.

The web-server uses the Set-Cookie header to set a cookie. Also, it may set the httpOnly option.

This option forbids any JavaScript access to the cookie. We can't see such a cookie or manipulate it using document.cookie.

That's used as a precaution measure, to protect from certain attacks when a hacker injects his own JavaScript code into a page and waits for a user to visit that page. That shouldn't be possible at all, hackers should not be able to inject their code into our site, but there may be bugs that let them do it.

Normally, if such a thing happens, and a user visits a web-page with hacker's JavaScript code, then that code executes and gains access to document.cookie with user cookies containing authentication information. That's bad.

But if a cookie is httpOnly, then document.cookie doesn't see it, so it is protected.

Here's a small set of functions to work with cookies, more convenient than a manual modification of document.cookie.

There exist many cookie libraries for that, so these are for demo purposes. Fully working though.

getCookie(name)

The shortest way to access a cookie is to use a regular expression.

The function getCookie(name) returns the cookie with the given name:

Here new RegExp is generated dynamically, to match ; name=<value>.

Please note that a cookie value is encoded, so getCookie uses a built-in decodeURIComponent function to decode it.

setCookie(name, value, options)

Sets the cookie's name to the given value with path=/ by default (can be modified to add other defaults):

run function setCookie(name, value, options = {}) {

options = { path: ‘/''', // add other defaults here if necessary …options };

if (options.expires instanceof Date) { options.expires = options.expires.toUTCString(); }

let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);

for (let optionKey in options) { updatedCookie += ";" + optionKey; let optionValue = options[optionKey]; if (optionValue !== true) { updatedCookie += "=" + optionValue; } }

document.cookie = updatedCookie; }

// Example of use: setCookie(‘user', ‘John', {secure: true, ‘max-age': 3600});

deleteCookie(name)

To delete a cookie, we can call it with a negative expiration date:

warn header="Updating or deleting must use same path and domain" Please note: when we update or delete a cookie, we should use exactly the same path and domain options as when we set it.

Together: cookie.js.

Appendix: Third-party cookies

A cookie is called "third-party" if it's placed by a domain other than the page the user is visiting.

For instance: 1. A page at site.com loads a banner from another site: <img src="https://ads.com/banner.png">. 2. Along with the banner, the remote server at ads.com may set the Set-Cookie header with a cookie like id=1234. Such a cookie originates from the ads.com domain, and will only be visible at ads.com:

                                                                                                                        ![](cookie-third-party.svg)
                                                                                                                      
  1. Next time when ads.com is accessed, the remote server gets the id cookie and recognizes the user:

  2. What's even more important is, when the user moves from site.com to another site other.com, which also has a banner, then ads.com gets the cookie, as it belongs to ads.com, thus recognizing the visitor and tracking him as he moves between sites:

Third-party cookies are traditionally used for tracking and ads services, due to their nature. They are bound to the originating domain, so ads.com can track the same user between different sites, if they all access it.

Naturally, some people don't like being tracked, so browsers allow to disable such cookies.

Also, some modern browsers employ special policies for such cookies: - Safari does not allow third-party cookies at all. - Firefox comes with a "black list" of third-party domains where it blocks third-party cookies.

                                                                                                                        If we load a script from a third-party domain, like `<script src="https://google-analytics.com/analytics.js">`, and that script uses `document.cookie` to set a cookie, then such cookie is not third-party.
If a script sets a cookie, then no matter where the script came from -- the cookie belongs to the domain of the current webpage.
                                                                                                                      

Appendix: GDPR

This topic is not related to JavaScript at all, just something to keep in mind when setting cookies.

There's a legislation in Europe called GDPR, that enforces a set of rules for websites to respect the users' privacy. One of these rules is to require an explicit permission for tracking cookies from the user.

Please note, that's only about tracking/identifying/authorizing cookies.

So, if we set a cookie that just saves some information, but neither tracks nor identifies the user, then we are free to do it.

But if we are going to set a cookie with an authentication session or a tracking id, then a user must allow that.

Websites generally have two variants of following GDPR. You must have seen them both already in the web:

  1. If a website wants to set tracking cookies only for authenticated users.

    To do so, the registration form should have a checkbox like "accept the privacy policy" (that describes how cookies are used), the user must check it, and then the website is free to set auth cookies.

  2. If a website wants to set tracking cookies for everyone.

    To do so legally, a website shows a modal "splash screen" for newcomers, and requires them to agree to the cookies. Then the website can set them and let people see the content. That can be disturbing for new visitors though. No one likes to see such "must-click" modal splash screens instead of the content. But GDPR requires an explicit agreement.

GDPR is not only about cookies, it's about other privacy-related issues too, but that's too much beyond our scope.

Summary

document.cookie provides access to cookies - write operations modify only cookies mentioned in it. - name/value must be encoded. - one cookie must not exceed 4KB, 20+ cookies per site (depends on the browser).

Cookie options: - path=/, by default current path, makes the cookie visible only under that path. - domain=site.com, by default a cookie is visible on the current domain only. If the domain is set explicitly, the cookie becomes visible on subdomains. - expires or max-age sets the cookie expiration time. Without them the cookie dies when the browser is closed. - secure makes the cookie HTTPS-only. - samesite forbids the browser to send the cookie with requests coming from outside the site. This helps to prevent XSRF attacks.

Additionally: - Third-party cookies may be forbidden by the browser, e.g. Safari does that by default. - When setting a tracking cookie for EU citizens, GDPR requires to ask for permission.

LocalStorage, sessionStorage

Web storage objects localStorage and sessionStorage allow to save key/value pairs in the browser.

What's interesting about them is that the data survives a page refresh (for sessionStorage) and even a full browser restart (for localStorage). We'll see that very soon.

We already have cookies. Why additional objects?

Both storage objects provide same methods and properties:

As you can see, it's like a Map collection ( setItem/getItem/removeItem), but also allows access by index with key(index).

Let's see how it works.

localStorage demo

The main features of localStorage are:

For instance, if you run this code…

js run localStorage.setItem('test', 1);

…And close/open the browser or just open the same page in a different window, then you can get it like this:

js run alert( localStorage.getItem('test') ); // 1

We only have to be on the same origin (domain/port/protocol), the url path can be different.

The localStorage is shared between all windows with the same origin, so if we set the data in one window, the change becomes visible in another one.

Object-like access

We can also use a plain object way of getting/setting keys, like this:

run // set key localStorage.test = 2;

// get key alert( localStorage.test ); // 2

// remove key delete localStorage.test;

That's allowed for historical reasons, and mostly works, but generally not recommended, because:

  1. If the key is user-generated, it can be anything, like length or toString, or another built-in method of localStorage. In that case getItem/setItem work fine, while object-like access fails: js run let key = 'length'; localStorage[key] = 5; // Error, can't assign length

  2. There's a storage event, it triggers when we modify the data. That event does not happen for object-like access. We'll see that later in this chapter.

Looping over keys

As we've seen, the methods provide "get/set/remove by key" functionality. But how to get all saved values or keys?

Unfortunately, storage objects are not iterable.

One way is to loop over them as over an array:

js run for(let i=0; i<localStorage.length; i++) { let key = localStorage.key(i); alert(`${key}: ${localStorage.getItem(key)}`); }

Another way is to use for key in localStorage loop, just as we do with regular objects.

It iterates over keys, but also outputs few built-in fields that we don't need:

js run // bad try for(let key in localStorage) { alert(key); // shows getItem, setItem and other built-in stuff }

…So we need either to filter fields from the prototype with hasOwnProperty check:

js run for(let key in localStorage) { if (!localStorage.hasOwnProperty(key)) { continue; // skip keys like "setItem", "getItem" etc } alert(`${key}: ${localStorage.getItem(key)}`); }

…Or just get the "own" keys with Object.keys and then loop over them if needed:

js run let keys = Object.keys(localStorage); for(let key of keys) { alert(`${key}: ${localStorage.getItem(key)}`); }

The latter works, because Object.keys only returns the keys that belong to the object, ignoring the prototype.

Strings only

Please note that both key and value must be strings.

If were any other type, like a number, or an object, it gets converted to string automatically:

js run sessionStorage.user = {name: "John"}; alert(sessionStorage.user); // [object Object]

We can use JSON to store objects though:

run sessionStorage.user = JSON.stringify({name: "John"});

// sometime later let user = JSON.parse( sessionStorage.user ); alert( user.name ); // John

Also it is possible to stringify the whole storage object, e.g. for debugging purposes:

js run // added formatting options to JSON.stringify to make the object look nicer alert( JSON.stringify(localStorage, null, 2) );

sessionStorage

The sessionStorage object is used much less often than localStorage.

Properties and methods are the same, but it's much more limited:

Let's see that in action.

Run this code…

js run sessionStorage.setItem('test', 1);

…Then refresh the page. Now you can still get the data:

js run alert( sessionStorage.getItem('test') ); // after refresh: 1

…But if you open the same page in another tab, and try again there, the code above returns null, meaning "nothing found".

That's exactly because sessionStorage is bound not only to the origin, but also to the browser tab. For that reason, sessionStorage is used sparingly.

Storage event

When the data gets updated in localStorage or sessionStorage, storage event triggers, with properties:

The important thing is: the event triggers on all window objects where the storage is accessible, except the one that caused it.

Let's elaborate.

Imagine, you have two windows with the same site in each. So localStorage is shared between them.

                                                                                                                        You might want to open this page in two browser windows to test the code below.
                                                                                                                      

If both windows are listening for window.onstorage, then each one will react on updates that happened in the other one.

run // triggers on updates made to the same storage from other documents window.onstorage = event => { // same as window.addEventListener(‘storage', event => { if (event.key != ‘now') return; alert(event.key + ‘:''' + event.newValue + " at " + event.url); };

localStorage.setItem(‘now', Date.now());

Please note that the event also contains: event.url - the url of the document where the data was updated.

Also, event.storageArea contains the storage object - the event is the same for both sessionStorage and localStorage, so event.storageArea references the one that was modified. We may even want to set something back in it, to "respond" to a change.

That allows different windows from the same origin to exchange messages.

Modern browsers also support Broadcast channel API, the special API for same-origin inter-window communication, it's more full featured, but less supported. There are libraries that polyfill that API, based on localStorage, that make it available everywhere.

Summary

Web storage objects localStorage and sessionStorage allow to store key/value in the browser. - Both key and value must be strings. - The limit is 5mb+, depends on the browser. - They do not expire. - The data is bound to the origin (domain/port/protocol).

localStorage sessionStorage
Shared between all tabs and windows with the same origin Visible within a browser tab, including iframes from the same origin
Survives browser restart Survives page refresh (but not tab close)

API:

Storage event:

libs: - ‘https://cdn.jsdelivr.net/npm/idb@3.0.2/build/idb.min.js'


IndexedDB

IndexedDB is a database that is built into browser, much more powerful than localStorage.

That power is usually excessive for traditional client-server apps. IndexedDB is intended for offline apps, to be combined with ServiceWorkers and other technologies.

The native interface to IndexedDB, described in the specification https://www.w3.org/TR/IndexedDB, is event-based.

We can also use async/await with the help of a promise-based wrapper, like https://github.com/jakearchibald/idb. That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases. So we'll start with events, and then, after we gain an understanding of IndexedDb, we'll use the wrapper.

smart header="Where's the data?" Technically, the data is usually stored in the visitor's home directory, along with browser settings, extensions, etc.

Different browsers and OS-level users have each their own independant storage.

Open database

To start working with IndexedDB, we first need to open (connect to) a database.

The syntax:

We can have many databases with different names, but all of them exist within the current origin (domain/protocol/port). Different websites can't access each other's databases.

The call returns openRequest object, we should listen to events on it: - success: database is ready, there's the "database object" in openRequest.result, we should use it for further calls. - error: opening failed. - upgradeneeded: database is ready, but its version is outdated (see below).

IndexedDB has a built-in mechanism of "schema versioning", absent in server-side databases.

Unlike server-side databases, IndexedDB is client-side, the data is stored in the browser, so we, developers, don't have full-time access to it. So, when we have published a new version of our app, and the user visits our webpage, we may need to update the database.

If the local database version is less than specified in open, then a special event upgradeneeded is triggered, and we can compare versions and upgrade data structures as needed.

The upgradeneeded event also triggers when the database doesn't yet exist (technically, it's version is 0), so we can perform the initialization.

Let's say we published the first version of our app.

Then we can open the database with version 1 and perform the initialization in an upgradeneeded handler like this:

Then, later, we publish the 2nd version.

We can open it with version 2 and perform the upgrade like this:

Please note: as our current version is 2, the onupgradeneeded handler has a code branch for version 0, suitable for users that are accessing for the first time and have no database, and also for version 1, for upgrades.

And then, only if onupgradeneeded handler finishes without errors, openRequest.onsuccess triggers, and the database is considered successfully opened.

To delete a database:

`` warn header="We can't open an older version of the database" If the current user database has a higher version than in theopen call, e.g. the existing DB version is3 , and we try toopen(…2) , then that's an error,openRequest.onerror` triggers.

That's rare, but such a thing may happen when a visitor loads outdated JavaScript code, e.g. from a proxy cache. So the code is old, but his database is new.

To protect from errors, we should check db.version and suggest a page reload. Use proper HTTP caching headers to avoid loading the old code, so that you'll never have such problems.

Parallel update problem

As we're talking about versioning, let's tackle a small related problem.

Let's say: 1. A visitor opened our site in a browser tab, with database version 1. 2. Then we rolled out an update, so our code is newer. 3. And then the same visitor opens our site in another tab.

So there's a tab with an open connection to DB version 1, while the second one attempts to update it to version 2 in its upgradeneeded handler.

The problem is that a database is shared between two tabs, as it's the same site, same origin. And it can't be both version 1 and 2. To perform the update to version 2, all connections to version 1 must be closed, including the one in the first tab.

In order to organize that, the versionchange event triggers on the "outdated" database object. We should listen for it and close the old database connection (and probably suggest a page reload, to load the updated code).

If we don't listen for the versionchange event and don't close the old connection, then the second, new connection won't be made. The openRequest object will emit the blocked event instead of success. So the second tab won't work.

Here's the code to correctly handle the parallel upgrade. It installs the onversionchange handler, that triggers if the current database connection becomes outdated (db version is updated elsewhere) and closes the connection.

…In other words, here we do two things:

  1. The db.onversionchange listener informs us about a parallel update attempt, if the current database version becomes outdated.
  2. The openRequest.onblocked listener informs us about the opposite situation: there's a connection to an outdated version elsewhere, and it doesn't close, so the newer connection can't be made.

We can handle things more gracefully in db.onversionchange, prompt the visitor to save the data before the connection is closed and so on.

Or, an alternative approach would be to not close the database in db.onversionchange, but instead use the onblocked handler (in the new tab) to alert the visitor, tell him that the newer version can't be loaded until they close other tabs.

These update collisions happen rarely, but we should at least have some handling for them, at least an onblocked handler, to prevent our script from dying silently.

Object store

To store something in IndexedDB, we need an object store.

An object store is a core concept of IndexedDB. Counterparts in other databases are called "tables" or "collections". It's where the data is stored. A database may have multiple stores: one for users, another one for goods, etc.

Despite being named an "object store", primitives can be stored too.

We can store almost any value, including complex objects.

IndexedDB uses the standard serialization algorithm to clone-and-store an object. It's like JSON.stringify, but more powerful, capable of storing much more datatypes.

An example of an object that can't be stored: an object with circular references. Such objects are not serializable. JSON.stringify also fails for such objects.

There must be a unique key for every value in the store.

A key must be one of these types - number, date, string, binary, or array. It's a unique identifier, so we can search/remove/update values by the key.

As we'll see very soon, we can provide a key when we add a value to the store, similar to localStorage. But when we store objects, IndexedDB allows setting up an object property as the key, which is much more convenient. Or we can auto-generate keys.

But we need to create an object store first.

The syntax to create an object store:

Please note, the operation is synchronous, no await needed.

If we don't supply keyOptions, then we'll need to provide a key explicitly later, when storing an object.

For instance, this object store uses id property as the key:

An object store can only be created/modified while updating the DB version, in upgradeneeded handler.

That's a technical limitation. Outside of the handler we'll be able to add/remove/update the data, but object stores can only be created/removed/altered during a version update.

To perform a database version upgrade, there are two main approaches: 1. We can implement per-version upgrade functions: from 1 to 2, from 2 to 3, from 3 to 4 etc. Then, in upgradeneeded we can compare versions (e.g. old 2, now 4) and run per-version upgrades step by step, for every intermediate version (2 to 3, then 3 to 4). 2. Or we can just examine the database: get a list of existing object stores as db.objectStoreNames. That object is a DOMStringList that provides contains(name) method to check for existance. And then we can do updates depending on what exists and what doesn't.

For small databases the second variant may be simpler.

Here's the demo of the second approach:

To delete an object store:

Transactions

The term "transaction" is generic, used in many kinds of databases.

A transaction is a group of operations, that should either all succeed or all fail.

For instance, when a person buys something, we need to: 1. Subtract the money from their account. 2. Add the item to their inventory.

It would be pretty bad if we complete the 1st operation, and then something goes wrong, e.g. lights out, and we fail to do the 2nd. Both should either succeed (purchase complete, good!) or both fail (at least the person kept their money, so they can retry).

Transactions can guarantee that.

All data operations must be made within a transaction in IndexedDB.

To start a transaction:

There's also versionchange transaction type: such transactions can do everything, but we can't create them manually. IndexedDB automatically creates a versionchange transaction when opening the database, for updateneeded handler. That's why it's a single place where we can update the database structure, create/remove object stores.

`` smart header="Why are there different types of transactions?" Performance is the reason why transactions need to be labeled eitherreadonly andreadwrite`.

Many readonly transactions are able to access the same store concurrently, but readwrite transactions can't. A readwrite transaction "locks" the store for writing. The next transaction must wait before the previous one finishes before accessing the same store.

After the transaction is created, we can add an item to the store, like this:

There were basically four steps:

  1. Create a transaction, mentioning all the stores it's going to access, at (1).
  2. Get the store object using transaction.objectStore(name), at (2).
  3. Perform the request to the object store books.add(book), at (3).
  4. …Handle request success/error (4), then we can make other requests if needed, etc.

Object stores support two methods to store a value:

Similar to opening a database, we can send a request: books.add(book), and then wait for success/error events.

Transactions' autocommit

In the example above we started the transaction and made add request. But as we stated previously, a transaction may have multiple associated requests, that must either all succeed or all fail. How do we mark the transaction as finished, with no more requests to come?

The short answer is: we don't.

In the next version 3.0 of the specification, there will probably be a manual way to finish the transaction, but right now in 2.0 there isn't.

When all transaction requests are finished, and the microtasks queue is empty, it is committed automatically.

Usually, we can assume that a transaction commits when all its requests are complete, and the current code finishes.

So, in the example above no special call is needed to finish the transaction.

Transactions auto-commit principle has an important side effect. We can't insert an async operation like fetch, setTimeout in the middle of a transaction. IndexedDB will not keep the transaction waiting till these are done.

In the code below, request2 in the line (*) fails, because the transaction is already committed, and can't make any request in it:

That's because fetch is an asynchronous operation, a macrotask. Transactions are closed before the browser starts doing macrotasks.

Authors of IndexedDB spec believe that transactions should be short-lived. Mostly for performance reasons.

Notably, readwrite transactions "lock" the stores for writing. So if one part of the application initiated readwrite on books object store, then another part that wants to do the same has to wait: the new transaction "hangs" till the first one is done. That can lead to strange delays if transactions take a long time.

So, what to do?

In the example above we could make a new db.transaction right before the new request (*).

But it will be even better, if we'd like to keep the operations together, in one transaction, to split apart IndexedDB transactions and "other" async stuff.

First, make fetch, prepare the data if needed, afterwards create a transaction and perform all the database requests, it'll work then.

To detect the moment of successful completion, we can listen to transaction.oncomplete event:

Only complete guarantees that the transaction is saved as a whole. Individual requests may succeed, but the final write operation may go wrong (e.g. I/O error or something).

To manually abort the transaction, call:

That cancels all modification made by the requests in it and triggers transaction.onabort event.

Error handling

Write requests may fail.

That's to be expected, not only because of possible errors at our side, but also for reasons not related to the transaction itself. For instance, the storage quota may be exceeded. So we must be ready to handle such case.

A failed request automatically aborts the transaction, canceling all its changes.

In some situations, we may want to handle the failure (e.g. try another request), without canceling existing changes, and continue the transaction. That's possible. The request.onerror handler is able to prevent the transaction abort by calling event.preventDefault().

In the example below a new book is added with the same key ( id) as the existing one. The store.add method generates a "ConstraintError" in that case. We handle it without canceling the transaction:

Event delegation

Do we need onerror/onsuccess for every request? Not every time. We can use event delegation instead.

IndexedDB events bubble: request -> transaction -> database.

All events are DOM events, with capturing and bubbling, but usually only bubbling stage is used.

So we can catch all errors using db.onerror handler, for reporting or other purposes:

…But what if an error is fully handled? We don't want to report it in that case.

We can stop the bubbling and hence db.onerror by using event.stopPropagation() in request.onerror.

Searching

There are two main types of search in an object store:

  1. By a key value or a key range. In our "books" storage that would be a value or range of values of book.id.
  2. By another object field, e.g. book.price. This required an additional data structure, named "index".

By key

First let's deal with the first type of search: by key.

Searching methods support both exact key values and so-called "ranges of values" - IDBKeyRange objects that specify an acceptable "key range".

IDBKeyRange objects are created using following calls:

We'll see practical examples of using them very soon.

To perform the actual search, there are following methods. They accept a query argument that can be either an exact key or a key range:

For instance, we have a lot of books in our store. Remember, the id field is the key, so all these methods can search by id.

Request examples:

smart header="Object store is always sorted" An object store sorts values by key internally.

So requests that return many values always return them in sorted by key order.

By a field using an index

To search by other object fields, we need to create an additional data structure named "index".

An index is an "add-on" to the store that tracks a given object field. For each value of that field, it stores a list of keys for objects that have that value. There will be a more detailed picture below.

The syntax:

In our example, we store books keyed by id.

Let's say we want to search by price.

First, we need to create an index. It must be done in upgradeneeded, just like an object store:

Imagine that our inventory has 4 books. Here's the picture that shows exactly what the index is:

As said, the index for each value of price (second argument) keeps the list of keys that have that price.

The index keeps itself up to date automatically, we don't have to care about it.

Now, when we want to search for a given price, we simply apply the same search methods to the index:

We can also use IDBKeyRange to create ranges and looks for cheap/expensive books:

Indexes are internally sorted by the tracked object field, price in our case. So when we do the search, the results are also sorted by price.

Deleting from store

The delete method looks up values to delete by a query, the call format is similar to getAll:

For instance:

If we'd like to delete books based on a price or another object field, then we should first find the key in the index, and then call delete:

To delete everything:

Cursors

Methods like getAll/getAllKeys return an array of keys/values.

But an object storage can be huge, bigger than the available memory. Then getAll will fail to get all records as an array.

What to do?

Cursors provide the means to work around that.

A cursor is a special object that traverses the object storage, given a query, and returns one key/value at a time, thus saving memory.

As an object store is sorted internally by key, a cursor walks the store in key order (ascending by default).

The syntax:

The main difference of the cursor is that request.onsuccess triggers multiple times: once for each result.

Here's an example of how to use a cursor:

The main cursor methods are:

Whether there are more values matching the cursor or not - onsuccess gets called, and then in result we can get the cursor pointing to the next record, or undefined.

In the example above the cursor was made for the object store.

But we also can make a cursor over an index. As we remember, indexes allow to search by an object field. Cursors over indexes do precisely the same as over object stores - they save memory by returning one value at a time.

For cursors over indexes, cursor.key is the index key (e.g. price), and we should use cursor.primaryKey property for the object key:

Promise wrapper

Adding onsuccess/onerror to every request is quite a cumbersome task. Sometimes we can make our life easier by using event delegation, e.g. set handlers on the whole transactions, but async/await is much more convenient.

Let's use a thin promise wrapper https://github.com/jakearchibald/idb further in this chapter. It creates a global idb object with promisified IndexedDB methods.

Then, instead of onsuccess/onerror we can write like this:

So we have all the sweet "plain async code" and "try..catch" stuff.

Error handling

If we don't catch an error, then it falls through, till the closest outer try..catch.

An uncaught error becomes an "unhandled promise rejection" event on window object.

We can handle such errors like this:

"Inactive transaction" pitfall

As we already know, a transaction auto-commits as soon as the browser is done with the current code and microtasks. So if we put a macrotask like fetch in the middle of a transaction, then the transaction won't wait for it to finish. It just auto-commits. So the next request in it would fail.

For a promise wrapper and async/await the situation is the same.

Here's an example of fetch in the middle of the transaction:

The next inventory.add after fetch (*) fails with an "inactive transaction" error, because the transaction is already committed and closed at that time.

The workaround is the same as when working with native IndexedDB: either make a new transaction or just split things apart. 1. Prepare the data and fetch all that's needed first. 2. Then save in the database.

Getting native objects

Internally, the wrapper performs a native IndexedDB request, adding onerror/onsuccess to it, and returns a promise that rejects/resolves with the result.

That works fine most of the time. The examples are at the lib page https://github.com/jakearchibald/idb.

In few rare cases, when we need the original request object, we can access it as promise.request property of the promise:

Summary

IndexedDB can be thought of as a "localStorage on steroids". It's a simple key-value database, powerful enough for offline apps, yet simple to use.

The best manual is the specification, the current one is 2.0, but few methods from 3.0 (it's not much different) are partially supported.

The basic usage can be described with a few phrases:

  1. Get a promise wrapper like idb.
  2. Open a database: idb.openDb(name, version, onupgradeneeded)
    • Create object storages and indexes in onupgradeneeded handler or perform version update if needed.
  3. For requests:
    • Create transaction db.transaction('books') (readwrite if needed).
    • Get the object store transaction.objectStore('books').
  4. Then, to search by a key, call methods on the object store directly.
    • To search by an object field, create an index.
  5. If the data does not fit in memory, use a cursor.

Here's a small demo app:

[codetabs src="books" current="index.html"]

Bezier curve

Bezier curves are used in computer graphics to draw shapes, for CSS animation and in many other places.

They are a very simple thing, worth to study once and then feel comfortable in the world of vector graphics and advanced animations.

Control points

A bezier curve is defined by control points.

There may be 2, 3, 4 or more.

For instance, two points curve:

Three points curve:

Four points curve:

If you look closely at these curves, you can immediately notice:

  1. Points are not always on curve. That's perfectly normal, later we'll see how the curve is built.
  2. The curve order equals the number of points minus one. For two points we have a linear curve (that's a straight line), for three points - quadratic curve (parabolic), for four points - cubic curve.
  3. A curve is always inside the convex hull of control points:

Because of that last property, in computer graphics it's possible to optimize intersection tests. If convex hulls do not intersect, then curves do not either. So checking for the convex hulls intersection first can give a very fast "no intersection" result. Checking the intersection of convex hulls is much easier, because they are rectangles, triangles and so on (see the picture above), much simpler figures than the curve.

The main value of Bezier curves for drawing - by moving the points the curve is changing in intuitively obvious way.

Try to move control points using a mouse in the example below:

[iframe src="demo.svg?nocpath=1&p=0,0,0.5,0,0.5,1,1,1" height=370]

As you can notice, the curve stretches along the tangential lines 1 -> 2 and 3 -> 4.

After some practice it becomes obvious how to place points to get the needed curve. And by connecting several curves we can get practically anything.

Here are some examples:

De Casteljau's algorithm

There's a mathematical formula for Bezier curves, but let's cover it a bit later, because De Casteljau's algorithm is identical to the mathematical definition and visually shows how it is constructed.

First let's see the 3-points example.

Here's the demo, and the explanation follow.

Control points (1,2 and 3) can be moved by the mouse. Press the "play" button to run it.

[iframe src="demo.svg?p=0,0,0.5,1,1,0&animate=1" height=370]

De Casteljau's algorithm of building the 3-point bezier curve:

  1. Draw control points. In the demo above they are labeled: 1, 2, 3.
  2. Build segments between control points 1 -> 2 -> 3. In the demo above they are brown.
  3. The parameter t moves from 0 to 1. In the example above the step 0.05 is used: the loop goes over 0, 0.05, 0.1, 0.15, ... 0.95, 1.

    For each of these values of t:

    • On each brown segment we take a point located on the distance proportional to t from its beginning. As there are two segments, we have two points.

      For instance, for t=0 - both points will be at the beginning of segments, and for t=0.25 - on the 25% of segment length from the beginning, for t=0.5 - 50%(the middle), for t=1 - in the end of segments.

    • Connect the points. On the picture below the connecting segment is painted blue.

For t=0.25 For t=0.5
  1. Now in the blue segment take a point on the distance proportional to the same value of t. That is, for t=0.25 (the left picture) we have a point at the end of the left quarter of the segment, and for t=0.5 (the right picture) - in the middle of the segment. On pictures above that point is red.

  2. As t runs from 0 to 1, every value of t adds a point to the curve. The set of such points forms the Bezier curve. It's red and parabolic on the pictures above.

That was a process for 3 points. But the same is for 4 points.

The demo for 4 points (points can be moved by a mouse):

[iframe src="demo.svg?p=0,0,0.5,0,0.5,1,1,1&animate=1" height=370]

The algorithm for 4 points:

The algorithm is recursive and can be generalized for any number of control points.

Given N of control points:

  1. We connect them to get initially N-1 segments.
  2. Then for each t from 0 to 1, we take a point on each segment on the distance proportional to t and connect them. There will be N-2 segments.
  3. Repeat step 2 until there is only one point.

These points make the curve.

                                                                                                                        **Run and pause examples to clearly see the segments and how the curve is built.**
                                                                                                                      

A curve that looks like y=1/t:

[iframe src="demo.svg?p=0,0,0,0.75,0.25,1,1,1&animate=1" height=370]

Zig-zag control points also work fine:

[iframe src="demo.svg?p=0,0,1,0.5,0,0.5,1,1&animate=1" height=370]

Making a loop is possible:

[iframe src="demo.svg?p=0,0,1,0.5,0,1,0.5,0&animate=1" height=370]

A non-smooth Bezier curve (yeah, that's possible too):

[iframe src="demo.svg?p=0,0,1,1,0,1,1,0&animate=1" height=370]

                                                                                                                        If there's something unclear in the algorithm description, please look at the live examples above to see how
the curve is built.
                                                                                                                      

As the algorithm is recursive, we can build Bezier curves of any order, that is: using 5, 6 or more control points. But in practice many points are less useful. Usually we take 2-3 points, and for complex lines glue several curves together. That's simpler to develop and calculate.

smart header="How to draw a curve through given points?" To specify a Bezier curve, control points are used. As we can see, they are not on the curve, except the first and the last ones.

Sometimes we have another task: to draw a curve through several points, so that all of them are on a single smooth curve. That task is called interpolation, and here we don't cover it.

There are mathematical formulas for such curves, for instance Lagrange polynomial. In computer graphics spline interpolation is often used to build smooth curves that connect many points.

Maths

A Bezier curve can be described using a mathematical formula.

As we saw - there's actually no need to know it, most people just draw the curve by moving points with a mouse. But if you're into maths - here it is.

Given the coordinates of control points P i : the first control point has coordinates P 1 = (x 1, y 1) , the second: P 2 = (x 2, y 2) , and so on, the curve coordinates are described by the equation that depends on the parameter t from the segment [0,1].

These are vector equations. In other words, we can put x and y instead of P to get corresponding coordinates.

For instance, the 3-point curve is formed by points (x,y) calculated as:

Instead of x 1, y 1, x 2, y 2, x 3, y 3 we should put coordinates of 3 control points, and then as t moves from 0 to 1, for each value of t we'll have (x,y) of the curve.

For instance, if control points are (0,0), (0.5, 1) and (1, 0), the equations become:

Now as t runs from 0 to 1, the set of values (x,y) for each t forms the curve for such control points.

Summary

Bezier curves are defined by their control points.

We saw two definitions of Bezier curves:

  1. Using a drawing process: De Casteljau's algorithm.
  2. Using a mathematical formulas.

Good properties of Bezier curves:

Usage:

CSS-animations

CSS animations make it possible to do simple animations without JavaScript at all.

JavaScript can be used to control CSS animations and make them even better, with little code.

CSS transitions [#css-transition]

The idea of CSS transitions is simple. We describe a property and how its changes should be animated. When the property changes, the browser paints the animation.

That is, all we need is to change the property, and the fluid transition will be done by the browser.

For instance, the CSS below animates changes of background-color for 3 seconds:

Now if an element has .animated class, any change of background-color is animated during 3 seconds.

Click the button below to animate the background:

run autorun height=60

There are 4 properties to describe CSS transitions:

We'll cover them in a moment, for now let's note that the common transition property allows declaring them together in the order: property duration timing-function delay, as well as animating multiple properties at once.

For instance, this button animates both color and font-size:

run height=80 autorun no-beautify

Now, let's cover animation properties one by one.

transition-property

In transition-property, we write a list of properties to animate, for instance: left, margin-left, height, color. Or we could write all, which means "animate all properties".

Do note that, there are properties which can not be animated. However, most of the generally used properties are animatable.

transition-duration

In transition-duration we can specify how long the animation should take. The time should be in CSS time format: in seconds s or milliseconds ms.

transition-delay

In transition-delay we can specify the delay before the animation. For instance, if transition-delay is 1s and transition-duration is 2s, then the animation starts 1 second after the property change and the total duration will be 2 seconds.

Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if transition-delay is -1s and transition-duration is 2s, then animation starts from the halfway point and total duration will be 1 second.

Here the animation shifts numbers from 0 to 9 using CSS translate property:

[codetabs src="digits"]

The transform property is animated like this:

In the example above JavaScript adds the class .animate to the element - and the animation starts:

We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative transition-delay.

Here if you click the digit - it starts the animation from the current second:

[codetabs src="digits-negative-delay"]

JavaScript does it with an extra line:

transition-timing-function

The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa.

It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it.

That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often.

Bezier curve

The timing function can be set as a Bezier curve with 4 control points that satisfy the conditions:

  1. First control point: (0,0).
  2. Last control point: (1,1).
  3. For intermediate points, the values of x must be in the interval 0..1, y can be anything.

The syntax for a Bezier curve in CSS: cubic-bezier(x2, y2, x3, y3). Here we need to specify only 2nd and 3rd control points, because the 1st one is fixed to (0,0) and the 4th one is (1,1).

The timing function describes how fast the animation process goes.

The simplest variant is when the animation goes uniformly, with the same linear speed. That can be specified by the curve cubic-bezier(0, 0, 1, 1).

Here's how that curve looks:

…As we can see, it's just a straight line. As the time ( x) passes, the completion ( y) of the animation steadily goes from 0 to 1.

The train in the example below goes from left to right with the permanent speed (click it):

[codetabs src="train-linear"]

The CSS transition is based on that curve:

…And how can we show a train slowing down?

We can use another Bezier curve: cubic-bezier(0.0, 0.5, 0.5 ,1.0).

The graph:

As we can see, the process starts fast: the curve soars up high, and then slower and slower.

Here's the timing function in action (click the train):

[codetabs src="train"]

CSS:

There are several built-in curves: linear, ease, ease-in, ease-out and ease-in-out.

The linear is a shorthand for cubic-bezier(0, 0, 1, 1) - a straight line, which we described above.

Other names are shorthands for the following cubic-bezier:

ease * ease-in ease-out ease-in-out
(0.25, 0.1, 0.25, 1.0) (0.42, 0, 1.0, 1.0) (0, 0, 0.58, 1.0) (0.42, 0, 0.58, 1.0)
ease, figure ease-in, figure ease-out, figure ease-in-out, figure

* - by default, if there's no timing function, ease is used.

So we could use ease-out for our slowing down train:

But it looks a bit differently.

A Bezier curve can make the animation exceed its range.

The control points on the curve can have any y coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range.

In the example below the animation code is:

The property left should animate from 100px to 400px.

But if you click the train, you'll see that:

[codetabs src="train-over"]

Why it happens is pretty obvious if we look at the graph of the given Bezier curve:

We moved the y coordinate of the 2nd point below zero, and for the 3rd point we made it over 1, so the curve goes out of the "regular" quadrant. The y is out of the "standard" range 0..1.

As we know, y measures "the completion of the animation process". The value y = 0 corresponds to the starting property value and y = 1 - the ending value. So values y<0 move the property beyond the starting left and y>1 - past the final left.

That's a "soft" variant for sure. If we put y values like -99 and 99 then the train would jump out of the range much more.

But how do we make a Bezier curve for a specific task? There are many tools. For instance, we can do it on the site http://cubic-bezier.com/.

Steps

The timing function steps(number of steps[, start/end]) allows splitting an animation into steps.

Let's see that in an example with digits.

Here's a list of digits, without any animations, just as a source:

[codetabs src="step-list"]

We'll make the digits appear in a discrete way by making the part of the list outside of the red "window" invisible and shifting the list to the left with each step.

There will be 9 steps, a step-move for each digit:

In action:

[codetabs src="step"]

The first argument of steps(9, start) is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so transition: 9s gives us 9 seconds for the whole animation - 1 second per digit.

The second argument is one of two words: start or end.

The start means that in the beginning of animation we need to make the first step immediately.

We can observe that during the animation: when we click on the digit it changes to 1 (the first step) immediately, and then changes in the beginning of the next second.

The process is progressing like this:

The alternative value end would mean that the change should be applied not in the beginning, but at the end of each second.

So the process would go like this:

Here's steps(9, end) in action (note the pause between the first digit change):

[codetabs src="step-end"]

There are also shorthand values:

These values are rarely used, because that's not really animation, but rather a single-step change.

Event transitionend

When the CSS animation finishes the transitionend event triggers.

It is widely used to do an action after the animation is done. Also we can join animations.

For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right:

[iframe src="boat" height=300 edit link]

The animation is initiated by the function go that re-runs each time the transition finishes, and flips the direction:

The event object for transitionend has a few specific properties:

event.propertyName
The property that has finished animating. Can be good if we animate multiple properties simultaneously.
event.elapsedTime
The time (in seconds) that the animation took, without transition-delay.

Keyframes

We can join multiple simple animations together using the @keyframes CSS rule.

It specifies the "name" of the animation and rules - what, when and where to animate. Then using the animation property, we can attach the animation to the element and specify additional parameters for it.

Here's an example with explanations:

run height=60 autorun="no-epub" no-beautify

There are many articles about @keyframes and a detailed specification.

You probably won't need @keyframes often, unless everything is in constant motion on your sites.

Summary

CSS animations allow smoothly (or not) animated changes of one or multiple CSS properties.

They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that.

Limitations of CSS animations compared to JavaScript animations:

compare plus="CSS animations" minus="JavaScript animations" + Simple things done simply. + Fast and lightweight for CPU. - JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. - Not just property changes. We can create new elements in JavaScript as part of the animation.

The majority of animations can be implemented using CSS as described in this chapter. And the transitionend event allows JavaScript to be run after the animation, so it integrates fine with the code.

But in the next chapter we'll do some JavaScript animations to cover more complex cases.

JavaScript animations

JavaScript animations can handle things that CSS can't.

For instance, moving along a complex path, with a timing function different from Bezier curves, or an animation on a canvas.

Using setInterval

An animation can be implemented as a sequence of frames - usually small changes to HTML/CSS properties.

For instance, changing style.left from 0px to 100px moves the element. And if we increase it in setInterval, changing by 2px with a tiny delay, like 50 times per second, then it looks smooth. That's the same principle as in the cinema: 24 frames per second is enough to make it look smooth.

The pseudo-code can look like this:

More complete example of the animation:

Click for the demo:

[codetabs height=200 src="move"]

Using requestAnimationFrame

Let's imagine we have several animations running simultaneously.

If we run them separately, then even though each one has setInterval(..., 20), then the browser would have to repaint much more often than every 20ms.

That's because they have different starting time, so "every 20ms" differs between different animations. The intervals are not aligned. So we'll have several independent runs within 20ms.

In other words, this:

…Is lighter than three independent calls:

These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother.

There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every 20ms.

But how do we know about that in JavaScript? There's a specification Animation timing that provides the function requestAnimationFrame. It addresses all these issues and even more.

The syntax:

That schedules the callback function to run in the closest time when the browser wants to do animation.

If we do changes in elements in callback then they will be grouped together with other requestAnimationFrame callbacks and with CSS animations. So there will be one geometry recalculation and repaint instead of many.

The returned value requestId can be used to cancel the call:

The callback gets one argument - the time passed from the beginning of the page load in microseconds. This time can also be obtained by calling performance.now().

Usually callback runs very soon, unless the CPU is overloaded or the laptop battery is almost discharged, or there's another reason.

The code below shows the time between first 10 runs for requestAnimationFrame. Usually it's 10-20ms:

run height=40 refresh

Structured animation

Now we can make a more universal animation function based on requestAnimationFrame:

Function animate accepts 3 parameters that essentially describes the animation:

duration
Total time of animation. Like, 1000.
timing(timeFraction)

Timing function, like CSS-property transition-timing-function that gets the fraction of time that passed ( 0 at start, 1 at the end) and returns the animation completion (like y on the Bezier curve).

For instance, a linear function means that the animation goes on uniformly with the same speed:

It's graph:

That's just like transition-timing-function: linear. There are more interesting variants shown below.

draw(progress)

The function that takes the animation completion state and draws it. The value progress=0 denotes the beginning animation state, and progress=1 - the end state.

This is that function that actually draws out the animation.

It can move the element:

…Or do anything else, we can animate anything, in any way.

Let's animate the element width from 0 to 100% using our function.

Click on the element for the demo:

[codetabs height=60 src="width"]

The code for it:

Unlike CSS animation, we can make any timing function and any drawing function here. The timing function is not limited by Bezier curves. And draw can go beyond properties, create new elements for like fireworks animation or something.

Timing functions

We saw the simplest, linear timing function above.

Let's see more of them. We'll try movement animations with different timing functions to see how they work.

Power of n

If we want to speed up the animation, we can use progress in the power n.

For instance, a parabolic curve:

The graph:

See in action (click to activate):

[iframe height=40 src="quad" link]

…Or the cubic curve or even greater n. Increasing the power makes it speed up faster.

Here's the graph for progress in the power 5:

In action:

[iframe height=40 src="quint" link]

The arc

Function:

The graph:

[iframe height=40 src="circ" link]

Back: bow shooting

This function does the "bow shooting". First we "pull the bowstring", and then "shoot".

Unlike previous functions, it depends on an additional parameter x, the "elasticity coefficient". The distance of "bowstring pulling" is defined by it.

The code:

The graph for x = 1.5:

For animation we use it with a specific value of x. Example for x = 1.5:

[iframe height=40 src="back" link]

Bounce

Imagine we are dropping a ball. It falls down, then bounces back a few times and stops.

The bounce function does the same, but in the reverse order: "bouncing" starts immediately. It uses few special coefficients for that:

In action:

[iframe height=40 src="bounce" link]

Elastic animation

One more "elastic" function that accepts an additional parameter x for the "initial range".

The graph for x=1.5:

In action for x=1.5:

[iframe height=40 src="elastic" link]

Reversal: ease*

So we have a collection of timing functions. Their direct application is called "easeIn".

Sometimes we need to show the animation in the reverse order. That's done with the "easeOut" transform.

easeOut

In the "easeOut" mode the timing function is put into a wrapper timingEaseOut:

In other words, we have a "transform" function makeEaseOut that takes a "regular" timing function and returns the wrapper around it:

For instance, we can take the bounce function described above and apply it:

Then the bounce will be not in the beginning, but at the end of the animation. Looks even better:

[codetabs src="bounce-easeout"]

Here we can see how the transform changes the behavior of the function:

If there's an animation effect in the beginning, like bouncing - it will be shown at the end.

In the graph above the regular bounce has the red color, and the easeOut bounce is blue.

easeInOut

We also can show the effect both in the beginning and the end of the animation. The transform is called "easeInOut".

Given the timing function, we calculate the animation state like this:

The wrapper code:

In action, bounceEaseInOut:

[codetabs src="bounce-easeinout"]

The "easeInOut" transform joins two graphs into one: easeIn (regular) for the first half of the animation and easeOut (reversed) - for the second part.

The effect is clearly seen if we compare the graphs of easeIn, easeOut and easeInOut of the circ timing function:

As we can see, the graph of the first half of the animation is the scaled down easeIn, and the second half is the scaled down easeOut. As a result, the animation starts and finishes with the same effect.

More interesting "draw"

Instead of moving the element we can do something else. All we need is to write the proper draw.

Here's the animated "bouncing" text typing:

[codetabs src="text"]

Summary

For animations that CSS can't handle well, or those that need tight control, JavaScript can help. JavaScript animations should be implemented via requestAnimationFrame. That built-in method allows to setup a callback function to run when the browser will be preparing a repaint. Usually that's very soon, but the exact time depends on the browser.

When a page is in the background, there are no repaints at all, so the callback won't run: the animation will be suspended and won't consume resources. That's great.

Here's the helper animate function to setup most animations:

Options:

Surely we could improve it, add more bells and whistles, but JavaScript animations are not applied on a daily basis. They are used to do something interesting and non-standard. So you'd want to add the features that you need when you need them.

JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here.

The same is about draw: we can animate anything, not just CSS properties.

Logical operators

There are four logical operators in JavaScript: || (OR), && (AND), ! (NOT), ?? (Nullish Coalescing). Here we cover the first three, the ?? operator is in the next article.

Although they are called "logical", they can be applied to values of any type, not only boolean. Their result can also be of any type.

Let's see the details.

|| (OR)

The "OR" operator is represented with two vertical line symbols:

In classical programming, the logical OR is meant to manipulate boolean values only. If any of its arguments are true, it returns true, otherwise it returns false.

In JavaScript, the operator is a little bit trickier and more powerful. But first, let's see what happens with boolean values.

There are four possible logical combinations:

js run alert( true || true ); // true alert( false || true ); // true alert( true || false ); // true alert( false || false ); // false

As we can see, the result is always true except for the case when both operands are false.

If an operand is not a boolean, it's converted to a boolean for the evaluation.

For instance, the number 1 is treated as true, the number 0 as false:

js run if (1 || 0) { // works just like if( true || false ) alert( 'truthy!' ); }

Most of the time, OR || is used in an if statement to test if any of the given conditions is true.

For example:

run let hour = 9;

! if (hour < 10 || hour > 18) { /! alert( ‘The office is closed.''' ); }

We can pass more conditions:

run let hour = 12; let isWeekend = true;

if (hour < 10 || hour > 18 || isWeekend) { alert( ‘The office is closed.''' ); // it is the weekend }

OR "||" finds the first truthy value [#or-finds-the-first-truthy-value]

The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript.

The extended algorithm works as follows.

Given multiple OR'ed values:

The OR || operator does the following:

A value is returned in its original form, without the conversion.

In other words, a chain of OR || returns the first truthy value or the last one if no truthy value is found.

For instance:

run alert( 1 || 0 ); // 1 (1 is truthy)

alert( null || 1 ); // 1 (1 is the first truthy value) alert( null || 0 || 1 ); // 1 (the first truthy value)

alert( undefined || null || 0 ); // 0 (all falsy, returns the last value)

This leads to some interesting usage compared to a "pure, classical, boolean-only OR".

  1. Getting the first truthy value from a list of variables or expressions.

    For instance, we have firstName, lastName and nickName variables, all optional (i.e. can be undefined or have falsy values).

    Let's use OR || to choose the one that has the data and show it (or "Anonymous" if nothing set):

    run let firstName = ""; let lastName =""; let nickName ="SuperCoder";

    ! alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder /!

    If all variables were falsy, "Anonymous" would show up.

  2. Short-circuit evaluation.

    Another feature of OR || operator is the so-called "short-circuit" evaluation.

    It means that || processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument.

    That importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call.

    In the example below, only the second message is printed:

    js run no-beautify *!*true*/!* || alert("not printed"); *!*false*/!* || alert("printed");

    In the first line, the OR || operator stops the evaluation immediately upon seeing true, so the alert isn't run.

    Sometimes, people use this feature to execute commands only if the condition on the left part is falsy.

&& (AND)

The AND operator is represented with two ampersands &&:

In classical programming, AND returns true if both operands are truthy and false otherwise:

js run alert( true && true ); // true alert( false && true ); // false alert( true && false ); // false alert( false && false ); // false

An example with if:

run let hour = 12; let minute = 30;

if (hour == 12 && minute == 30) { alert( ‘The time is 12:30' ); }

Just as with OR, any value is allowed as an operand of AND:

js run if (1 && 0) { // evaluated as true && false alert( "won't work, because the result is falsy" ); }

AND "&&" finds the first falsy value

Given multiple AND'ed values:

The AND && operator does the following:

In other words, AND returns the first falsy value or the last value if none were found.

The rules above are similar to OR. The difference is that AND returns the first falsy value while OR returns the first truthy one.

Examples:

run // if the first operand is truthy, // AND returns the second operand: alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5

// if the first operand is falsy, // AND returns it. The second operand is ignored alert( null && 5 ); // null alert( 0 && "no matter what" ); // 0

We can also pass several values in a row. See how the first falsy one is returned:

js run alert( 1 && 2 && null && 3 ); // null

When all values are truthy, the last value is returned:

js run alert( 1 && 2 && 3 ); // 3, the last one

smart header="Precedence of AND&& is higher than OR|| " The precedence of AND&& operator is higher than OR||`.

So the code a && b || c && d is essentially the same as if the && expressions were in parentheses: (a && b) || (c && d).

warn header="Don't replaceif with|| or&& " Sometimes, people use the AND&& operator as a "shorter way to writeif`".

For instance:

run let x = 1;

(x > 0) && alert( ‘Greater than zero!''' );

The action in the right part of && would execute only if the evaluation reaches it. That is, only if (x > 0) is true.

So we basically have an analogue for:

run let x = 1;

if (x > 0) alert( ‘Greater than zero!''' );

                                                                                                                        
Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND.
                                                                                                                      

! (NOT)

The boolean NOT operator is represented with an exclamation sign !.

The syntax is pretty simple:

The operator accepts a single argument and does the following:

  1. Converts the operand to boolean type: true/false.
  2. Returns the inverse value.

For instance:

js run alert( !true ); // false alert( !0 ); // true

A double NOT !! is sometimes used for converting a value to boolean type:

js run alert( !!"non-empty string" ); // true alert( !!null ); // false

That is, the first NOT converts the value to boolean and returns the inverse, and the second NOT inverses it again. In the end, we have a plain value-to-boolean conversion.

There's a little more verbose way to do the same thing - a built-in Boolean function:

js run alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false

The precedence of NOT ! is the highest of all logical operators, so it always executes first, before && or ||.

From the orbital height

This section describes a set of modern standards for "web components".

As of now, these standards are under development. Some features are well-supported and integrated into the modern HTML/DOM standard, while others are yet in draft stage. You can try examples in any browser, Google Chrome is probably the most up to date with these features. Guess, that's because Google fellows are behind many of the related specifications.

What's common between…

The whole component idea is nothing new. It's used in many frameworks and elsewhere.

Before we move to implementation details, take a look at this great achievement of humanity:

That's the International Space Station (ISS).

And this is how it's made inside (approximately):

The International Space Station: - Consists of many components. - Each component, in its turn, has many smaller details inside. - The components are very complex, much more complicated than most websites. - Components are developed internationally, by teams from different countries, speaking different languages.

…And this thing flies, keeps humans alive in space!

How such complex devices are created?

Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it.

Component architecture

The well known rule for developing complex software is: don't make complex software.

If something becomes complex - split it into simpler parts and connect in the most obvious way.

A good architect is the one who can make the complex simple.

We can split user interface into visual components: each of them has own place on the page, can "do" a well-described task, and is separate from the others.

Let's take a look at a website, for example Twitter.

It naturally splits into components:

  1. Top navigation.
  2. User info.
  3. Follow suggestions.
  4. Submit form.
  5. (and also 6, 7) - messages.

Components may have subcomponents, e.g. messages may be parts of a higher-level "message list" component. A clickable user picture itself may be a component, and so on.

How do we decide, what is a component? That comes from intuition, experience and common sense. Usually it's a separate visual entity that we can describe in terms of what it does and how it interacts with the page. In the case above, the page has blocks, each of them plays its own role, it's logical to make these components.

A component has: - Its own JavaScript class. - DOM structure, managed solely by its class, outside code doesn't access it ("encapsulation" principle). - CSS styles, applied to the component. - API: events, class methods etc, to interact with other components.

Once again, the whole "component" thing is nothing special.

There exist many frameworks and development methodologies to build them, each with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" - CSS scoping and DOM encapsulation.

"Web components" provide built-in browser capabilities for that, so we don't have to emulate them any more.

In the next chapter we'll go into details of "Custom Elements" - the fundamental and well-supported feature of web components, good on its own.

Custom elements

We can create custom HTML elements, described by our class, with its own methods and properties, events and so on.

Once a custom element is defined, we can use it on par with built-in HTML elements.

That's great, as HTML dictionary is rich, but not infinite. There are no <easy-tabs>, <sliding-carousel>, <beautiful-upload>… Just think of any other tag we might need.

We can define them with a special class, and then use as if they were always a part of HTML.

There are two kinds of custom elements:

  1. Autonomous custom elements - "all-new" elements, extending the abstract HTMLElement class.
  2. Customized built-in elements - extending built-in elements, like a customized button, based on HTMLButtonElement etc.

First we'll cover autonomous elements, and then move to customized built-in ones.

To create a custom element, we need to tell the browser several details about it: how to show it, what to do when the element is added or removed to page, etc.

That's done by making a class with special methods. That's easy, as there are only few methods, and all of them are optional.

Here's a sketch with the full list:

After that, we need to register the element:

Now for any HTML elements with tag <my-element>, an instance of MyElement is created, and the aforementioned methods are called. We also can document.createElement('my-element') in JavaScript.

`` smart header="Custom element name must contain a hyphen- " Custom element name must have a hyphen- , e.g.my-element andsuper-button are valid names, butmyelement` is not.

That's to ensure that there are no name conflicts between built-in and custom HTML elements.

Example: "time-formatted"

For example, there already exists <time> element in HTML, for date/time. But it doesn't do any formatting by itself.

Let's create <time-formatted> element that displays the time in a nice, language-aware format:

run height=50 autorun="no-epub"

! <time-formatted datetime="2019-12-01" /! year="numeric" month="long" day="numeric" hour="numeric" minute="numeric" second="numeric" time-zone-name="short" >

  1. The class has only one method connectedCallback() - the browser calls it when <time-formatted> element is added to page (or when HTML parser detects it), and it uses the built-in Intl.DateTimeFormat data formatter, well-supported across the browsers, to show a nicely formatted time.
  2. We need to register our new element by customElements.define(tag, class).
  3. And then we can use it everywhere.

`` smart header="Custom elements upgrade" If the browser encounters any elements beforecustomElements.define`, that's not an error. But the element is yet unknown, just like any non-standard tag.

Such "undefined" elements can be styled with CSS selector :not(:defined).

When customElement.define is called, they are "upgraded": a new instance of TimeFormatted is created for each, and connectedCallback is called. They become :defined.

To get the information about custom elements, there are methods: - customElements.get(name) - returns the class for a custom element with the given name, - customElements.whenDefined(name) - returns a promise that resolves (without value) when a custom element with the given name becomes defined.

`` smart header="Rendering inconnectedCallback , not inconstructor " In the example above, element content is rendered (created) inconnectedCallback`.

Why not in the constructor?

The reason is simple: when constructor is called, it's yet too early. The element is created, but the browser did not yet process/assign attributes at this stage: calls to getAttribute would return null. So we can't really render there.

Besides, if you think about it, that's better performance-wise - to delay the work until it's really needed.

The connectedCallback triggers when the element is added to the document. Not just appended to another element as a child, but actually becomes a part of the page. So we can build detached DOM, create elements and prepare them for later use. They will only be actually rendered when they make it into the page.

Observing attributes

In the current implementation of <time-formatted>, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like a.href, we expect the change to be immediately visible. So let's fix this.

We can observe attributes by providing their list in observedAttributes() static getter. For such attributes, attributeChangedCallback is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons).

Here's a new <time-formatted>, that auto-updates when attributes change:

run autorun="no-epub" height=50

  1. The rendering logic is moved to render() helper method.
  2. We call it once when the element is inserted into page.
  3. For a change of an attribute, listed in observedAttributes(), attributeChangedCallback triggers.
  4. …and re-renders the element.
  5. At the end, we can easily make a live timer.

Rendering order

When HTML parser builds the DOM, elements are processed one after another, parents before children. E.g. if we have <outer><inner></inner></outer>, then <outer> element is created and connected to DOM first, and then <inner>.

That leads to important consequences for custom elements.

For example, if a custom element tries to access innerHTML in connectedCallback, it gets nothing:

run height=40

! John /!

If you run it, the alert is empty.

That's exactly because there are no children on that stage, the DOM is unfinished. HTML parser connected the custom element <user-info>, and is going to proceed to its children, but just didn't yet.

If we'd like to pass information to custom element, we can use attributes. They are available immediately.

Or, if we really need the children, we can defer access to them with zero-delay setTimeout.

This works:

run height=40

! John /!

Now the alert in line (*) shows "John", as we run it asynchronously, after the HTML parsing is complete. We can process children if needed and finish the initialization.

On the other hand, this solution is also not perfect. If nested custom elements also use setTimeout to initialize themselves, then they queue up: the outer setTimeout triggers first, and then the inner one.

So the outer element finishes the initialization before the inner one.

Let's demonstrate that on example:

`` html run height=0 <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { alert( ${this.id} connected.`); setTimeout(() => alert(`${this.id} initialized.`)); } });

! /!

Output order:

  1. outer connected.
  2. inner connected.
  3. outer initialized.
  4. inner initialized.

We can clearly see that the outer element finishes initialization (3) before the inner one (4).

There's no built-in callback that triggers after nested elements are ready. If needed, we can implement such thing on our own. For instance, inner elements can dispatch events like initialized, and outer ones can listen and react on them.

Customized built-in elements

New elements that we create, such as <time-formatted>, don't have any associated semantics. They are unknown to search engines, and accessibility devices can't handle them.

But such things can be important. E.g, a search engine would be interested to know that we actually show a time. And if we're making a special kind of button, why not reuse the existing <button> functionality?

We can extend and customize built-in HTML elements by inheriting from their classes.

For example, buttons are instances of HTMLButtonElement, let's build upon it.

  1. Extend HTMLButtonElement with our class:

  2. Provide the third argument to customElements.define, that specifies the tag: js customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*);

    There may be different tags that share the same DOM-class, that's why specifying extends is needed.

  3. At the end, to use our custom element, insert a regular <button> tag, but add is="hello-button" to it: html <button is="hello-button">...</button>

Here's a full example:

run autorun="no-epub"

! /!

! /!

Our new button extends the built-in one. So it keeps the same styles and standard features like disabled attribute.

References

Summary

Custom elements can be of two types:

  1. "Autonomous" - new tags, extending HTMLElement.

    Definition scheme:

  2. "Customized built-in elements" - extensions of existing elements.

    Requires one more .define argument, and is="..." in HTML: js class MyButton extends HTMLButtonElement { /*...*/ } customElements.define('my-button', MyElement, {extends: 'button'}); /* <button is="my-button"> */

Custom elements are well-supported among browsers. There's a polyfill https://github.com/webcomponents/polyfills/tree/master/packages/webcomponentsjs.

Shadow DOM

Shadow DOM serves for encapsulation. It allows a component to have its very own "shadow" DOM tree, that can't be accidentally accessed from the main document, may have local style rules, and more.

Built-in shadow DOM

Did you ever think how complex browser controls are created and styled?

Such as <input type="range">:

The browser uses DOM/CSS internally to draw them. That DOM structure is normally hidden from us, but we can see it in developer tools. E.g. in Chrome, we need to enable in Dev Tools "Show user agent shadow DOM" option.

Then <input type="range"> looks like this:

What you see under #shadow-root is called "shadow DOM".

We can't get built-in shadow DOM elements by regular JavaScript calls or selectors. These are not regular children, but a powerful encapsulation technique.

In the example above, we can see a useful attribute pseudo. It's non-standard, exists for historical reasons. We can use it style subelements with CSS, like this:

run autorun

Once again, pseudo is a non-standard attribute. Chronologically, browsers first started to experiment with internal DOM structures to implement controls, and then, after time, shadow DOM was standardized to allow us, developers, to do the similar thing.

Further on, we'll use the modern shadow DOM standard, covered by DOM spec and other related specifications.

Shadow tree

A DOM element can have two types of DOM subtrees:

  1. Light tree - a regular DOM subtree, made of HTML children. All subtrees that we've seen in previous chapters were "light".
  2. Shadow tree - a hidden DOM subtree, not reflected in HTML, hidden from prying eyes.

If an element has both, then the browser renders only the shadow tree. But we can setup a kind of composition between shadow and light trees as well. We'll see the details later in the chapter info:slots-composition.

Shadow tree can be used in Custom Elements to hide component internals and apply component-local styles.

For example, this <show-hello> element hides its internal DOM in shadow tree:

`` html run autorun height=60 <script> customElements.define('show-hello', class extends HTMLElement { connectedCallback() { const shadow = this.attachShadow({mode: 'open'}); shadow.innerHTML =

Hello, ${this.getAttribute(‘name')}

`; }
});

That's how the resulting DOM looks in Chrome dev tools, all the content is under "#shadow-root":

First, the call to elem.attachShadow({mode: …}) creates a shadow tree.

There are two limitations: 1. We can create only one shadow root per element. 2. The elem must be either a custom element, or one of: "article", "aside", "blockquote", "body", "div", "footer", "h1..h6", "header", "main" "nav", "p", "section", or "span". Other elements, like <img>, can't host shadow tree.

The mode option sets the encapsulation level. It must have any of two values: - "open" - the shadow root is available as elem.shadowRoot.

                                                                                                                        Any code is able to access the shadow tree of `elem`.   
                                                                                                                      

The shadow root, returned by attachShadow, is like an element: we can use innerHTML or DOM methods, such as append, to populate it.

The element with a shadow root is called a "shadow tree host", and is available as the shadow root host property:

Encapsulation

Shadow DOM is strongly delimited from the main document:

  1. Shadow DOM elements are not visible to querySelector from the light DOM. In particular, Shadow DOM elements may have ids that conflict with those in the light DOM. They must be unique only within the shadow tree.
  2. Shadow DOM has own stylesheets. Style rules from the outer DOM don't get applied.

For example:

run untrusted height=40

  1. The style from the document does not affect the shadow tree.
  2. …But the style from the inside works.
  3. To get elements in shadow tree, we must query from inside the tree.

References

Summary

Shadow DOM is a way to create a component-local DOM.

  1. shadowRoot = elem.attachShadow({mode: open|closed}) - creates shadow DOM for elem. If mode="open", then it's accessible as elem.shadowRoot property.
  2. We can populate shadowRoot using innerHTML or other DOM methods.

Shadow DOM elements: - Have their own ids space, - Invisible to JavaScript selectors from the main document, such as querySelector, - Use styles only from the shadow tree, not from the main document.

Shadow DOM, if exists, is rendered by the browser instead of so-called "light DOM" (regular children). In the chapter info:slots-composition we'll see how to compose them.

Template element

A built-in <template> element serves as a storage for HTML markup templates. The browser ignores it contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.

In theory, we could create any invisible element somewhere in HTML for HTML markup storage purposes. What's special about <template>?

First, its content can be any valid HTML, even if it normally requires a proper enclosing tag.

For example, we can put there a table row <tr>:

Usually, if we try to put <tr> inside, say, a <div>, the browser detects the invalid DOM structure and "fixes" it, adds <table> around. That's not what we want. On the other hand, <template> keeps exactly what we place there.

We can put styles and scripts into <template> as well:

The browser considers <template> content "out of the document": styles are not applied, scripts are not executed, <video autoplay> is not run, etc.

The content becomes live (styles apply, scripts run etc) when we insert it into the document.

Inserting template

The template content is available in its content property as a DocumentFragment - a special type of DOM node.

We can treat it as any other DOM node, except one special property: when we insert it somewhere, its children are inserted instead.

For example:

run

Let's rewrite a Shadow DOM example from the previous chapter using <template>:

run untrusted autorun="no-epub" height=60

Click me

In the line (*) when we clone and insert tmpl.content, as its DocumentFragment, its children ( <style>, <p>) are inserted instead.

They form the shadow DOM:

Summary

To summarize:

The <template> tag is quite unique, because:

The <template> element does not feature any iteration mechanisms, data binding or variable substitutions, but we can implement those on top of it.

Shadow DOM slots, composition

Many types of components, such as tabs, menus, image galleries, and so on, need the content to render.

Just like built-in browser <select> expects <option> items, our <custom-tabs> may expect the actual tab content to be passed. And a <custom-menu> may expect menu items.

The code that makes use of <custom-menu> can look like this:

…Then our component should render it properly, as a nice menu with given title and items, handle menu events, etc.

How to implement it?

We could try to analyze the element content and dynamically copy-rearrange DOM nodes. That's possible, but if we're moving elements to shadow DOM, then CSS styles from the document do not apply in there, so the visual styling may be lost. Also that requires some coding.

Luckily, we don't have to. Shadow DOM supports <slot> elements, that are automatically filled by the content from light DOM.

Named slots

Let's see how slots work on a simple example.

Here, <user-card> shadow DOM provides two slots, filled from light DOM:

`` html run autorun="no-epub" untrusted height=80 <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML =
Name: ! /!
                                                                                                                              <div>Birthday:
                                                                                                                          
! /!
                                                                                                                          `;
                                                                                                                        
} });

<span !slot="username" /!>John Smith <span !slot="birthday" /!>01.01.2001

In the shadow DOM, <slot name="X"> defines an "insertion point", a place where elements with slot="X" are rendered.

Then the browser performs "composition": it takes elements from the light DOM and renders them in corresponding slots of the shadow DOM. At the end, we have exactly what we want - a component that can be filled with data.

Here's the DOM structure after the script, not taking composition into account:

We created the shadow DOM, so here it is, under #shadow-root. Now the element has both light and shadow DOM.

For rendering purposes, for each <slot name="..."> in shadow DOM, the browser looks for slot="..." with the same name in the light DOM. These elements are rendered inside the slots:

The result is called "flattened" DOM:

…But the flattened DOM exists only for rendering and event-handling purposes. It's kind of "virtual". That's how things are shown. But the nodes in the document are actually not moved around!

That can be easily checked if we run querySelectorAll: nodes are still at their places.

So, the flattened DOM is derived from shadow DOM by inserting slots. The browser renders it and uses for style inheritance, event propagation (more about that later). But JavaScript still sees the document "as is", before flattening.

warn header="Only top-level children may have slot=\"...\" attribute" Theslot="…" attribute is only valid for direct children of the shadow host (in our example, ` element). For nested elements it's ignored.

For example, the second <span> here is ignored (as it's not a top-level child of <user-card>):

If there are multiple elements in light DOM with the same slot name, they are appended into the slot, one after another.

For example, this:

Gives this flattened DOM with two elements in <slot name="username">:

Slot fallback content

If we put something inside a <slot>, it becomes the fallback, "default" content. The browser shows it if there's no corresponding filler in light DOM.

For example, in this piece of shadow DOM, Anonymous renders if there's no slot="username" in light DOM.

Default slot: first unnamed

The first <slot> in shadow DOM that doesn't have a name is a "default" slot. It gets all nodes from the light DOM that aren't slotted elsewhere.

For example, let's add the default slot to our <user-card> that shows all unslotted information about the user:

`` html run autorun="no-epub" untrusted height=140 <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML =
Name:
                                                                                                                      <div>Birthday:
<slot name="birthday"></slot>
</div>
<fieldset>
<legend>Other information</legend>
                                                                                                                    
! /!
                                                                                                                    `;
                                                                                                                  
} }); !
I like to swim.
/! John Smith 01.01.2001 !
…And play volleyball too!

/!

All the unslotted light DOM content gets into the "Other information" fieldset.

Elements are appended to a slot one after another, so both unslotted pieces of information are in the default slot together.

The flattened DOM looks like this:

                                                                                                                    
                                                                                                                      
                                                                                                                        <user-card>
                                                                                                                      
                                                                                                                        #shadow-root
                                                                                                                      
                                                                                                                        <div>Name:
                                                                                                                      
                                                                                                                      
                                                                                                                        <slot
                                                                                                                         name=
                                                                                                                        "username"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <span
                                                                                                                         slot=
                                                                                                                        "username"
                                                                                                                        >John Smith
                                                                                                                        </span>
                                                                                                                      
                                                                                                                      
                                                                                                                        </slot>
                                                                                                                      
                                                                                                                      
                                                                                                                        </div>
                                                                                                                      
                                                                                                                      
                                                                                                                        <div>Birthday:
                                                                                                                      
                                                                                                                      
                                                                                                                        <slot
                                                                                                                         name=
                                                                                                                        "birthday"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <span
                                                                                                                         slot=
                                                                                                                        "birthday"
                                                                                                                        >01.01.2001
                                                                                                                        </span>
                                                                                                                      
                                                                                                                      
                                                                                                                        </slot>
                                                                                                                      
                                                                                                                      
                                                                                                                        </div>
                                                                                                                      
                                                                                                                      
                                                                                                                        <fieldset>
                                                                                                                      
                                                                                                                      
                                                                                                                        <legend>Other information
                                                                                                                        </legend>
                                                                                                                      
                                                                                                                      *!*
                                                                                                                      
                                                                                                                        <slot>
                                                                                                                      
                                                                                                                      
                                                                                                                        <div>I like to swim.
                                                                                                                        </div>
                                                                                                                      
                                                                                                                      
                                                                                                                        <div>...And play volleyball too!
                                                                                                                        </div>
                                                                                                                      
                                                                                                                      
                                                                                                                        </slot>
                                                                                                                      
                                                                                                                      */!*
                                                                                                                      
                                                                                                                        </fieldset>
                                                                                                                      
                                                                                                                      
                                                                                                                        </user-card>
                                                                                                                      
                                                                                                                    
                                                                                                                  

Now let's back to <custom-menu>, mentioned at the beginning of the chapter.

We can use slots to distribute elements.

Here's the markup for <custom-menu>:

                                                                                                                    
                                                                                                                      
                                                                                                                        <custom-menu>
                                                                                                                      
                                                                                                                      
                                                                                                                        <span
                                                                                                                         slot=
                                                                                                                        "title"
                                                                                                                        >Candy menu
                                                                                                                        </span>
                                                                                                                      
                                                                                                                      
                                                                                                                        <li
                                                                                                                         slot=
                                                                                                                        "item"
                                                                                                                        >Lollipop
                                                                                                                        </li>
                                                                                                                      
                                                                                                                      
                                                                                                                        <li
                                                                                                                         slot=
                                                                                                                        "item"
                                                                                                                        >Fruit Toast
                                                                                                                        </li>
                                                                                                                      
                                                                                                                      
                                                                                                                        <li
                                                                                                                         slot=
                                                                                                                        "item"
                                                                                                                        >Cup Cake
                                                                                                                        </li>
                                                                                                                      
                                                                                                                      
                                                                                                                        </custom-menu>
                                                                                                                      
                                                                                                                    
                                                                                                                  

The shadow DOM template with proper slots:

                                                                                                                    
                                                                                                                      
                                                                                                                        <template
                                                                                                                         id=
                                                                                                                        "tmpl"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <style>
                                                                                                                        /* menu styles */
                                                                                                                        </style>
                                                                                                                      
                                                                                                                      
                                                                                                                        <div
                                                                                                                         class=
                                                                                                                        "menu"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <slot
                                                                                                                         name=
                                                                                                                        "title"
                                                                                                                        ></slot>
                                                                                                                      
                                                                                                                      
                                                                                                                        <ul><slot
                                                                                                                         name=
                                                                                                                        "item"
                                                                                                                        ></slot></ul>
                                                                                                                      
                                                                                                                      
                                                                                                                        </div>
                                                                                                                      
                                                                                                                      
                                                                                                                        </template>
                                                                                                                      
                                                                                                                    
                                                                                                                  
  1. <span slot="title"> goes into <slot name="title">.
  2. There are many <li slot="item"> in the template, but only one <slot name="item"> in the template. So all such <li slot="item"> are appended to <slot name="item"> one after another, thus forming the list.

The flattened DOM becomes:

                                                                                                                    
                                                                                                                      
                                                                                                                        <custom-menu>
                                                                                                                      
                                                                                                                        #shadow-root
                                                                                                                      
                                                                                                                        <style>
                                                                                                                        /* menu styles */
                                                                                                                        </style>
                                                                                                                      
                                                                                                                      
                                                                                                                        <div
                                                                                                                         class=
                                                                                                                        "menu"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <slot
                                                                                                                         name=
                                                                                                                        "title"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <span
                                                                                                                         slot=
                                                                                                                        "title"
                                                                                                                        >Candy menu
                                                                                                                        </span>
                                                                                                                      
                                                                                                                      
                                                                                                                        </slot>
                                                                                                                      
                                                                                                                      
                                                                                                                        <ul>
                                                                                                                      
                                                                                                                      
                                                                                                                        <slot
                                                                                                                         name=
                                                                                                                        "item"
                                                                                                                        >
                                                                                                                      
                                                                                                                      
                                                                                                                        <li
                                                                                                                         slot=
                                                                                                                        "item"
                                                                                                                        >Lollipop
                                                                                                                        </li>
                                                                                                                      
                                                                                                                      
                                                                                                                        <li
                                                                                                                         slot=
                                                                                                                        "item"
                                                                                                                        >Fruit Toast
                                                                                                                        </li>
                                                                                                                      
                                                                                                                      
                                                                                                                        <li
                                                                                                                         slot=
                                                                                                                        "item"
                                                                                                                        >Cup Cake
                                                                                                                        </li>
                                                                                                                      
                                                                                                                      
                                                                                                                        </slot>
                                                                                                                      
                                                                                                                      
                                                                                                                        </ul>
                                                                                                                      
                                                                                                                      
                                                                                                                        </div>
                                                                                                                      
                                                                                                                      
                                                                                                                        </custom-menu>
                                                                                                                      
                                                                                                                    
                                                                                                                  

One might notice that, in a valid DOM, <li> must be a direct child of <ul>. But that's flattened DOM, it describes how the component is rendered, such thing happens naturally here.

We just need to add a click handler to open/close the list, and the <custom-menu> is ready:

                                                                                                                    
                                                                                                                      
                                                                                                                        customElements.
                                                                                                                        define(
                                                                                                                        'custom-menu'
                                                                                                                        ,
                                                                                                                        class
                                                                                                                        extends HTMLElement 
                                                                                                                        {
                                                                                                                      
                                                                                                                      
                                                                                                                        connectedCallback() 
                                                                                                                        {
                                                                                                                      
                                                                                                                      
                                                                                                                        this.
                                                                                                                        attachShadow(
                                                                                                                        {
                                                                                                                        mode
                                                                                                                        :
                                                                                                                        'open'
                                                                                                                        })
                                                                                                                        ;
                                                                                                                      
                                                                                                                      
                                                                                                                      
                                                                                                                        // tmpl is the shadow DOM template (above)
                                                                                                                      
                                                                                                                      
                                                                                                                        this.
                                                                                                                        shadowRoot.
                                                                                                                        append( 
                                                                                                                        tmpl.
                                                                                                                        content.
                                                                                                                        cloneNode(
                                                                                                                        true) )
                                                                                                                        ;
                                                                                                                      
                                                                                                                      
                                                                                                                      
                                                                                                                        // we can't select light DOM nodes, so let's handle clicks on the slot
                                                                                                                      
                                                                                                                      
                                                                                                                        this.
                                                                                                                        shadowRoot.
                                                                                                                        querySelector(
                                                                                                                        'slot[name="title"]').
                                                                                                                        onclick
                                                                                                                        = () 
                                                                                                                        =>
                                                                                                                        {
                                                                                                                      
                                                                                                                      
                                                                                                                        // open/close the menu
                                                                                                                      
                                                                                                                      
                                                                                                                        this.
                                                                                                                        shadowRoot.
                                                                                                                        querySelector(
                                                                                                                        '.menu').
                                                                                                                        classList.
                                                                                                                        toggle(
                                                                                                                        'closed')
                                                                                                                        ;
                                                                                                                      
                                                                                                                      
                                                                                                                        };
                                                                                                                      
                                                                                                                      
                                                                                                                        }
                                                                                                                      
                                                                                                                      
                                                                                                                        })
                                                                                                                        ;
                                                                                                                      
                                                                                                                    
                                                                                                                  

Here's the full demo:

[iframe src="menu" height=140 edit]

Of course, we can add more functionality to it: events, methods and so on.

Updating slots

What if the outer code wants to add/remove menu items dynamically?

The browser monitors slots and updates the rendering if slotted elements are added/removed.

Also, as light DOM nodes are not copied, but just rendered in slots, the changes inside them immediately become visible.

So we don't have to do anything to update rendering. But if the component code wants to know about slot changes, then slotchange event is available.

For example, here the menu item is inserted dynamically after 1 second, and the title changes after 2 seconds:

run untrusted height=80 Candy menu

The menu rendering updates each time without our intervention.

There are two slotchange events here:

  1. At initialization:

    slotchange: title triggers immediately, as the slot="title" from the light DOM gets into the corresponding slot.
  2. After 1 second:

    slotchange: item triggers, when a new <li slot="item"> is added.

Please note: there's no slotchange event after 2 seconds, when the content of slot="title" is modified. That's because there's no slot change. We modify the content inside the slotted element, that's another thing.

If we'd like to track internal modifications of light DOM from JavaScript, that's also possible using a more generic mechanism: MutationObserver.

Slot API

Finally, let's mention the slot-related JavaScript methods.

As we've seen before, JavaScript looks at the "real" DOM, without flattening. But, if the shadow tree has {mode: 'open'}, then we can figure out which elements assigned to a slot and, vise-versa, the slot by the element inside it:

These methods are useful when we need not just show the slotted content, but also track it in JavaScript.

For example, if <custom-menu> component wants to know, what it shows, then it could track slotchange and get the items from slot.assignedElements:

run untrusted height=120 Candy menu
  • Lollipop
  • Fruit Toast
  • Summary

    Usually, if an element has shadow DOM, then its light DOM is not displayed. Slots allow to show elements from light DOM in specified places of shadow DOM.

    There are two kinds of slots:

    The process of rendering slotted elements inside their slots is called "composition". The result is called a "flattened DOM".

    Composition does not really move nodes, from JavaScript point of view the DOM is still same.

    JavaScript can access slots using methods: - slot.assignedNodes/Elements() - returns nodes/elements inside the slot. - node.assignedSlot - the reverse property, returns slot by a node.

    If we'd like to know what we're showing, we can track slot contents using: - slotchange event - triggers the first time a slot is filled, and on any add/remove/replace operation of the slotted element, but not its children. The slot is event.target. - MutationObserver to go deeper into slot content, watch changes inside it.

    Now, as we know how to show elements from light DOM in shadow DOM, let's see how to style them properly. The basic rule is that shadow elements are styled inside, and light elements - outside, but there are notable exceptions.

    We'll see the details in the next chapter.

    Shadow DOM styling

    Shadow DOM may include both <style> and <link rel="stylesheet" href="…"> tags. In the latter case, stylesheets are HTTP-cached, so they are not redownloaded for multiple components that use same template.

    As a general rule, local styles work only inside the shadow tree, and document styles work outside of it. But there are few exceptions.

    :host

    The :host selector allows to select the shadow host (the element containing the shadow tree).

    For instance, we're making <custom-dialog> element that should be centered. For that we need to style the <custom-dialog> element itself.

    That's exactly what :host does:

    run autorun="no-epub" untrusted height=80

    Hello!

    Cascading

    The shadow host ( <custom-dialog> itself) resides in the light DOM, so it's affected by document CSS rules.

    If there's a property styled both in :host locally, and in the document, then the document style takes precedence.

    For instance, if in the document we had:

    …Then the <custom-dialog> would be without padding.

    It's very convenient, as we can setup "default" component styles in its :host rule, and then easily override them in the document.

    The exception is when a local property is labelled !important, for such properties, local styles take precedence.

    :host(selector)

    Same as :host, but applied only if the shadow host matches the selector.

    For example, we'd like to center the <custom-dialog> only if it has centered attribute:

    run autorun="no-epub" untrusted height=80

    Centered!

    Not centered.

    Now the additional centering styles are only applied to the first dialog: <custom-dialog centered>.

    :host-context(selector)

    Same as :host, but applied only if the shadow host or any of its ancestors in the outer document matches the selector.

    E.g. :host-context(.dark-theme) matches only if there's dark-theme class on <custom-dialog> on anywhere above it:

    To summarize, we can use :host-family of selectors to style the main element of the component, depending on the context. These styles (unless !important) can be overridden by the document.

    Styling slotted content

    Now let's consider the situation with slots.

    Slotted elements come from light DOM, so they use document styles. Local styles do not affect slotted content.

    In the example below, slotted <span> is bold, as per document style, but does not take background from the local style: run autorun="no-epub" untrusted height=80
    ! John Smith /!

    The result is bold, but not red.

    If we'd like to style slotted elements in our component, there are two choices.

    First, we can style the <slot> itself and rely on CSS inheritance:

    run autorun="no-epub" untrusted height=80
    ! John Smith /!

    Here <p>John Smith</p> becomes bold, because CSS inheritance is in effect between the <slot> and its contents. But in CSS itself not all properties are inherited.

    Another option is to use ::slotted(selector) pseudo-class. It matches elements based on two conditions:

    1. That's a slotted element, that comes from the light DOM. Slot name doesn't matter. Just any slotted element, but only the element itself, not its children.
    2. The element matches the selector.

    In our example, ::slotted(div) selects exactly <div slot="username">, but not its children:

    run autorun="no-epub" untrusted height=80
                                                                                                                          <div>John Smith</div>
                                                                                                                        

    Please note, ::slotted selector can't descend any further into the slot. These selectors are invalid:

    Also, ::slotted can only be used in CSS. We can't use it in querySelector.

    CSS hooks with custom properties

    How do we style internal elements of a component from the main document?

    Selectors like :host apply rules to <custom-dialog> element or <user-card>, but how to style shadow DOM elements inside them?

    There's no selector that can directly affect shadow DOM styles from the document. But just as we expose methods to interact with our component, we can expose CSS variables (custom CSS properties) to style it.

    Custom CSS properties exist on all levels, both in light and shadow.

    For example, in shadow DOM we can use --user-card-field-color CSS variable to style fields, and the outer document can set its value:

    Then, we can declare this property in the outer document for <user-card>:

    Custom CSS properties pierce through shadow DOM, they are visible everywhere, so the inner .field rule will make use of it.

    Here's the full example:

    run autorun="no-epub" untrusted height=80

    John Smith 01.01.2001

    Summary

    Shadow DOM can include styles, such as <style> or <link rel="stylesheet">.

    Local styles can affect: - shadow tree, - shadow host with :host-family pseudoclasses, - slotted elements (coming from light DOM), ::slotted(selector) allows to select slotted elements themselves, but not their children.

    Document styles can affect: - shadow host (as it lives in the outer document) - slotted elements and their contents (as that's also in the outer document)

    When CSS properties conflict, normally document styles have precedence, unless the property is labelled as !important. Then local styles have precedence.

    CSS custom properties pierce through shadow DOM. They are used as "hooks" to style the component:

    1. The component uses a custom CSS property to style key elements, such as var(--component-name-title, <default value>).
    2. Component author publishes these properties for developers, they are same important as other public component methods.
    3. When a developer wants to style a title, they assign --component-name-title CSS property for the shadow host or above.
    4. Profit!

    Shadow DOM and events

    The idea behind shadow tree is to encapsulate internal implementation details of a component.

    Let's say, a click event happens inside a shadow DOM of <user-card> component. But scripts in the main document have no idea about the shadow DOM internals, especially if the component comes from a 3rd-party library.

    So, to keep the details encapsulated, the browser retargets the event.

    Events that happen in shadow DOM have the host element as the target, when caught outside of the component.

    Here's a simple example:

    run autorun="no-epub" untrusted height=60

    If you click on the button, the messages are:

    1. Inner target: BUTTON - internal event handler gets the correct target, the element inside shadow DOM.
    2. Outer target: USER-CARD - document event handler gets shadow host as the target.

    Event retargeting is a great thing to have, because the outer document doesn't have to know about component internals. From its point of view, the event happened on <user-card>.

    Retargeting does not occur if the event occurs on a slotted element, that physically lives in the light DOM.

    For example, if a user clicks on <span slot="username"> in the example below, the event target is exactly this span element, for both shadow and light handlers:

    run autorun="no-epub" untrusted height=60 ! John Smith /!

    If a click happens on "John Smith", for both inner and outer handlers the target is <span slot="username">. That's an element from the light DOM, so no retargeting.

    On the other hand, if the click occurs on an element originating from shadow DOM, e.g. on <b>Name</b>, then, as it bubbles out of the shadow DOM, its event.target is reset to <user-card>.

    Bubbling, event.composedPath()

    For purposes of event bubbling, flattened DOM is used.

    So, if we have a slotted element, and an event occurs somewhere inside it, then it bubbles up to the <slot> and upwards.

    The full path to the original event target, with all the shadow elements, can be obtained using event.composedPath(). As we can see from the name of the method, that path is taken after the composition.

    In the example above, the flattened DOM is:

    So, for a click on <span slot="username">, a call to event.composedPath() returns an array: [ span, slot, div, shadow-root, user-card, body, html, document, window]. That's exactly the parent chain from the target element in the flattened DOM, after the composition.

    `` warn header="Shadow tree details are only provided for{mode:‘open'} trees" If the shadow tree was created with{mode: ‘closed'} , then the composed path starts from the host:user-card` and upwards.

    That's the similar principle as for other methods that work with shadow DOM. Internals of closed trees are completely hidden.

    event.composed

    Most events successfully bubble through a shadow DOM boundary. There are few events that do not.

    This is governed by the composed event object property. If it's true, then the event does cross the boundary. Otherwise, it only can be caught from inside the shadow DOM.

    If you take a look at UI Events specification, most events have composed: true:

    • blur, focus, focusin, focusout,
    • click, dblclick,
    • mousedown, mouseup mousemove, mouseout, mouseover,
    • wheel,
    • beforeinput, input, keydown, keyup.

    All touch events and pointer events also have composed: true.

    There are some events that have composed: false though:

    • mouseenter, mouseleave (they do not bubble at all),
    • load, unload, abort, error,
    • select,
    • slotchange.

    These events can be caught only on elements within the same DOM, where the event target resides.

    Custom events

    When we dispatch custom events, we need to set both bubbles and composed properties to true for it to bubble up and out of the component.

    For example, here we create div#inner in the shadow DOM of div#outer and trigger two events on it. Only the one with composed: true makes it outside to the document:

    run untrusted height=0

    Summary

    Events only cross shadow DOM boundaries if their composed flag is set to true.

    Built-in events mostly have composed: true, as described in the relevant specifications:

    Some built-in events that have composed: false:

    • mouseenter, mouseleave (also do not bubble),
    • load, unload, abort, error,
    • select,
    • slotchange.

    These events can be caught only on elements within the same DOM.

    If we dispatch a CustomEvent, then we should explicitly set composed: true.

    Please note that in case of nested components, one shadow DOM may be nested into another. In that case composed events bubble through all shadow DOM boundaries. So, if an event is intended only for the immediate enclosing component, we can also dispatch it on the shadow host and set composed: false. Then it's out of the component shadow DOM, but won't bubble up to higher-level DOM.

    Patterns and flags

    Regular expressions are patterns that provide a powerful way to search and replace in text.

    In JavaScript, they are available via the RegExp object, as well as being integrated in methods of strings.

    Regular Expressions

    A regular expression (also "regexp", or just "reg") consists of a pattern and optional flags.

    There are two syntaxes that can be used to create a regular expression object.

    The "long" syntax:

    And the "short" one, using slashes "/":

    Slashes pattern:/.../ tell JavaScript that we are creating a regular expression. They play the same role as quotes for strings.

    In both cases regexp becomes an instance of the built-in RegExp class.

    The main difference between these two syntaxes is that pattern using slashes /.../ does not allow for expressions to be inserted (like string template literals with ${...}). They are fully static.

    Slashes are used when we know the regular expression at the code writing time - and that's the most common situation. While new RegExp is more often used when we need to create a regexp "on the fly" from a dynamically generated string. For instance:

    Flags

    Regular expressions may have flags that affect the search.

    There are only 6 of them in JavaScript:

    pattern:i
    With this flag the search is case-insensitive: no difference between A and a (see the example below).
    pattern:g
    With this flag the search looks for all matches, without it - only the first match is returned.
    pattern:m
    Multiline mode (covered in the chapter info:regexp-multiline-mode).
    pattern:s
    Enables "dotall" mode, that allows a dot pattern:. to match newline character \n (covered in the chapter info:regexp-character-classes).
    pattern:u
    Enables full Unicode support. The flag enables correct processing of surrogate pairs. More about that in the chapter info:regexp-unicode.
    pattern:y
    "Sticky" mode: searching at the exact position in the text (covered in the chapter info:regexp-sticky)

    smart header="Colors" From here on the color scheme is:

    • regexp - pattern:red
    • string (where we search) - subject:blue
    • result - match:green

    Searching: str.match

    As mentioned previously, regular expressions are integrated with string methods.

    The method str.match(regexp) finds all matches of regexp in the string str.

    It has 3 working modes:

    1. If the regular expression has flag pattern:g, it returns an array of all matches: run let str = "We will, we will rock you";

      alert( str.match(/we/gi) ); // We,we (an array of 2 substrings that match) `` Please note that bothmatch:We andmatch:we are found, because flagpattern:i` makes the regular expression case-insensitive.

    2. If there's no such flag it returns only the first match in the form of an array, with the full match at index 0 and some additional details in properties: run let str = "We will, we will rock you";

      let result = str.match(/we/i); // without flag g

      alert( result[0] ); // We (1st match) alert( result.length ); // 1

      // Details: alert( result.index ); // 0 (position of the match) alert( result.input ); // We will, we will rock you (source string) `` The array may have other indexes, besides0` if a part of the regular expression is enclosed in parentheses. We'll cover that in the chapter info:regexp-groups.

    3. And, finally, if there are no matches, null is returned (doesn't matter if there's flag pattern:g or not).

      This a very important nuance. If there are no matches, we don't receive an empty array, but instead receive null. Forgetting about that may lead to errors, e.g.:

      run let matches = "JavaScript".match(/HTML/); // = null

      if (!matches.length) { // Error: Cannot read property ‘length' of null alert("Error in the line above"); }

      If we'd like the result to always be an array, we can write it this way:

      run let matches = "JavaScript".match(/HTML/) ! || [] /!;

      if (!matches.length) { alert("No matches"); // now it works }

    Replacing: str.replace

    The method str.replace(regexp, replacement) replaces matches found using regexp in string str with replacement (all matches if there's flag pattern:g, otherwise, only the first one).

    For instance:

    run // no flag g alert( "We will, we will".replace(/we/i, "I") ); // I will, we will

    // with flag g alert( "We will, we will".replace(/we/ig, "I") ); // I will, I will

    The second argument is the replacement string. We can use special character combinations in it to insert fragments of the match:

    Symbols Action in the replacement string
    $& inserts the whole match
    $&#096;</code>|inserts a part of the string before the match| |`$''' |inserts a part of the string after the match| | $n`|if `n` is a 1-2 digit number, then it inserts the contents of n-th parentheses, more about it in the chapter <info:regexp-groups>| |`$ |inserts the contents of the parentheses with the givenname , more about it in the chapter <info:regexp-groups>| |$ $`|inserts character `$`

    An example with pattern:$&:

    js run alert( "I love HTML".replace(/HTML/, "$& and JavaScript") ); // I love HTML and JavaScript

    Testing: regexp.test

    The method regexp.test(str) looks for at least one match, if found, returns true, otherwise false.

    run let str = "I love JavaScript"; let regexp = /LOVE/i;

    alert( regexp.test(str) ); // true

    Later in this chapter we'll study more regular expressions, walk through more examples, and also meet other methods.

    Full information about the methods is given in the article info:regexp-methods.

    Summary

    • A regular expression consists of a pattern and optional flags: pattern:g, pattern:i, pattern:m, pattern:u, pattern:s, pattern:y.
    • Without flags and special symbols (that we'll study later), the search by a regexp is the same as a substring search.
    • The method str.match(regexp) looks for matches: all of them if there's pattern:g flag, otherwise, only the first one.
    • The method str.replace(regexp, replacement) replaces matches found using regexp with replacement: all of them if there's pattern:g flag, otherwise only the first one.
    • The method regexp.test(str) returns true if there's at least one match, otherwise, it returns false.

    Character classes

    Consider a practical task - we have a phone number like "+7(903)-123-45-67", and we need to turn it into pure numbers: 79031234567.

    To do so, we can find and remove anything that's not a number. Character classes can help with that.

    A character class is a special notation that matches any symbol from a certain set.

    For the start, let's explore the "digit" class. It's written as pattern:\d and corresponds to "any single digit".

    For instance, let's find the first digit in the phone number:

    run let str = "+7(903)-123-45-67";

    let regexp = /;

    alert( str.match(regexp) ); // 7

    Without the flag pattern:g, the regular expression only looks for the first match, that is the first digit pattern:\d.

    Let's add the pattern:g flag to find all digits:

    run let str = "+7(903)-123-45-67";

    let regexp = /g;

    alert( str.match(regexp) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7

    // let's make the digits-only phone number of them: alert( str.match(regexp).join('') ); // 79031234567

    That was a character class for digits. There are other character classes as well.

    Most used are:

    pattern:\d ("d" is from "digit")
    A digit: a character from 0 to 9.
    pattern:\s ("s" is from "space")
    A space symbol: includes spaces, tabs \t, newlines \n and few other rare characters, such as \v, \f and \r.
    pattern:\w ("w" is from "word")
    A "wordly" character: either a letter of Latin alphabet or a digit or an underscore _. Non-Latin letters (like cyrillic or hindi) do not belong to pattern:\w.

    For instance, pattern:\d\s\w means a "digit" followed by a "space character" followed by a "wordly character", such as match:1 a.

    A regexp may contain both regular symbols and character classes.

    For instance, pattern:CSS\d matches a string match:CSS with a digit after it:

    run let str = "Is there CSS4?"; let regexp = /CSS

    alert( str.match(regexp) ); // CSS4

    Also we can use many character classes:

    js run alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5'

    The match (each regexp character class has the corresponding result character):

    Inverse classes

    For every character class there exists an "inverse class", denoted with the same letter, but uppercased.

    The "inverse" means that it matches all other characters, for instance:

    pattern:\D
    Non-digit: any character except pattern:\d, for instance a letter.
    pattern:\S
    Non-space: any character except pattern:\s, for instance a letter.
    pattern:\W
    Non-wordly character: anything but pattern:\w, e.g a non-latin letter or a space.

    In the beginning of the chapter we saw how to make a number-only phone number from a string like subject:+7(903)-123-45-67: find all digits and join them.

    run let str = "+7(903)-123-45-67";

    alert( str.match(/g).join('') ); // 79031234567

    An alternative, shorter way is to find non-digits pattern:\D and remove them from the string:

    run let str = "+7(903)-123-45-67";

    alert( str.replace(//g, "") ); // 79031234567

    A dot is "any character"

    A dot pattern:. is a special character class that matches "any character except a newline".

    For instance:

    js run alert( "Z".match(/./) ); // Z

    Or in the middle of a regexp:

    run let regexp = /CS.4/;

    alert( "CSS4".match(regexp) ); // CSS4 alert( "CS-4".match(regexp) ); // CS-4 alert( "CS 4".match(regexp) ); // CS 4 (space is also a character)

    Please note that a dot means "any character", but not the "absence of a character". There must be a character to match it:

    js run alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot

    Dot as literally any character with "s" flag

    By default, a dot doesn't match the newline character \n.

    For instance, the regexp pattern:A.B matches match:A, and then match:B with any character between them, except a newline \n:

    js run alert( "A\nB".match(/A.B/) ); // null (no match)

    There are many situations when we'd like a dot to mean literally "any character", newline included.

    That's what flag pattern:s does. If a regexp has it, then a dot pattern:. matches literally any character:

    js run alert( "A\nB".match(/A.B/s) ); // A\nB (match!)

    warn header="Not supported in IE" Thepattern:s` flag is not supported in IE.

    Luckily, there's an alternative, that works everywhere. We can use a regexp like pattern:[\s\S] to match "any character" (this pattern will be covered in the article info:regexp-character-sets-and-ranges).

    js run alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!)

    The pattern pattern:[\s\S] literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as pattern:[\d\D], that doesn't matter. Or even the pattern:[^] - as it means match any character except nothing.

    Also we can use this trick if we want both kind of "dots" in the same pattern: the actual dot pattern:. behaving the regular way ("not including a newline"), and also a way to match "any character" with pattern:[\s\S] or alike.

    warn header="Pay attention to spaces" Usually we pay little attention to spaces. For us stringssubject:1-5 andsubject:1 - 5` are nearly identical.

    But if a regexp doesn't take spaces into account, it may fail to work.

    Let's try to find digits separated by a hyphen:

    js run alert( "1 - 5".match(/\d-\d/) ); // null, no match!

    Let's fix it adding spaces into the regexp pattern:\d - \d:

    js run alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works // or we can use \s class: alert( "1 - 5".match(/\d\s-\s\d/) ); // 1 - 5, also works

    A space is a character. Equal in importance with any other character.

    We can't add or remove spaces from a regular expression and expect it to work the same.

    In other words, in a regular expression all characters matter, spaces too.

    Summary

    There exist following character classes:

    • pattern:\d - digits.
    • pattern:\D - non-digits.
    • pattern:\s - space symbols, tabs, newlines.
    • pattern:\S - all but pattern:\s.
    • pattern:\w - Latin letters, digits, underscore '_'.
    • pattern:\W - all but pattern:\w.
    • pattern:. - any character if with the regexp 's' flag, otherwise any except a newline \n.

    …But that's not all!

    Unicode encoding, used by JavaScript for strings, provides many properties for characters, like: which language the letter belongs to (if it's a letter), is it a punctuation sign, etc.

    We can search by these properties as well. That requires flag pattern:u, covered in the next article.

    Unicode: flag "u" and class

    JavaScript uses Unicode encoding for strings. Most characters are encoded with 2 bytes, but that allows to represent at most 65536 characters.

    That range is not big enough to encode all possible characters, that's why some rare characters are encoded with 4 bytes, for instance like 𝒳 (mathematical X) or 😄 (a smile), some hieroglyphs and so on.

    Here are the Unicode values of some characters:

    Character Unicode Bytes count in Unicode
    a 0x0061 2
    0x2248 2
    𝒳 0x1d4b3 4
    𝒴 0x1d4b4 4
    😄 0x1f604 4

    So characters like a and occupy 2 bytes, while codes for 𝒳, 𝒴 and 😄 are longer, they have 4 bytes.

    Long time ago, when JavaScript language was created, Unicode encoding was simpler: there were no 4-byte characters. So, some language features still handle them incorrectly.

    For instance, length thinks that here are two characters:

    js run alert('😄'.length); // 2 alert('𝒳'.length); // 2

    …But we can see that there's only one, right? The point is that length treats 4 bytes as two 2-byte characters. That's incorrect, because they must be considered only together (so-called "surrogate pair", you can read about them in the article info:string).

    By default, regular expressions also treat 4-byte "long characters" as a pair of 2-byte ones. And, as it happens with strings, that may lead to odd results. We'll see that a bit later, in the article info:regexp-character-sets-and-ranges.

    Unlike strings, regular expressions have flag pattern:u that fixes such problems. With such flag, a regexp handles 4-byte characters correctly. And also Unicode property search becomes available, we'll get to it next.

    Unicode properties

    Every character in Unicode has a lot of properties. They describe what "category" the character belongs to, contain miscellaneous information about it.

    For instance, if a character has Letter property, it means that the character belongs to an alphabet (of any language). And Number property means that it's a digit: maybe Arabic or Chinese, and so on.

    We can search for characters with a property, written as pattern:\p{…}. To use pattern:\p{…}, a regular expression must have flag pattern:u.

    For instance, \p{Letter} denotes a letter in any language. We can also use \p{L}, as L is an alias of Letter. There are shorter aliases for almost every property.

    In the example below three kinds of letters will be found: English, Georgian and Korean.

    run let str = "A ბ ㄱ";

    alert( str.match(//gu) ); // A,ბ,ㄱ alert( str.match(//g) ); // null (no matches, doesn't work without the flag "u")

    Here's the main character categories and their subcategories:

    • Letter L:
      • lowercase Ll
      • modifier Lm,
      • titlecase Lt,
      • uppercase Lu,
      • other Lo.
    • Number N:
      • decimal digit Nd,
      • letter number Nl,
      • other No.
    • Punctuation P:
      • connector Pc,
      • dash Pd,
      • initial quote Pi,
      • final quote Pf,
      • open Ps,
      • close Pe,
      • other Po.
    • Mark M (accents etc):
      • spacing combining Mc,
      • enclosing Me,
      • non-spacing Mn.
    • Symbol S:
      • currency Sc,
      • modifier Sk,
      • math Sm,
      • other So.
    • Separator Z:
      • line Zl,
      • paragraph Zp,
      • space Zs.
    • Other C:
      • control Cc,
      • format Cf,
      • not assigned Cn,
      • private use Co,
      • surrogate Cs.

    So, e.g. if we need letters in lower case, we can write pattern:\p{Ll}, punctuation signs: pattern:\p{P} and so on.

    There are also other derived categories, like: - Alphabetic ( Alpha), includes Letters L, plus letter numbers Nl (e.g. Ⅻ - a character for the roman number 12), plus some other symbols Other_Alphabetic ( OAlpha). - Hex_Digit includes hexadecimal digits: 0-9, a-f. - …And so on.

    Unicode supports many different properties, their full list would require a lot of space, so here are the references:

    Example: hexadecimal numbers

    For instance, let's look for hexadecimal numbers, written as xFF, where F is a hex digit (0..1 or A..F).

    A hex digit can be denoted as pattern:\p{Hex_Digit}:

    run let regexp = /x/u;

    alert("number: xAF".match(regexp)); // xAF

    Example: Chinese hieroglyphs

    Let's look for Chinese hieroglyphs.

    There's a Unicode property Script (a writing system), that may have a value: Cyrillic, Greek, Arabic, Han (Chinese) and so on, here's the full list.

    To look for characters in a given writing system we should use pattern:Script=<value>, e.g. for Cyrillic letters: pattern:\p{sc=Cyrillic}, for Chinese hieroglyphs: pattern:\p{sc=Han}, and so on:

    run let regexp = //gu; // returns Chinese hieroglyphs

    let str = Hello Привет 你好 123_456;

    alert( str.match(regexp) ); // 你,好

    Example: currency

    Characters that denote a currency, such as $, , ¥, have Unicode property pattern:\p{Currency_Symbol}, the short alias: pattern:\p{Sc}.

    Let's use it to look for prices in the format "currency, followed by a digit":

    run let regexp = /gu;

    let str = Prices: $2, €1, ¥9;

    alert( str.match(regexp) ); // $2,€1,¥9

    Later, in the article info:regexp-quantifiers we'll see how to look for numbers that contain many digits.

    Summary

    Flag pattern:u enables the support of Unicode in regular expressions.

    That means two things:

    1. Characters of 4 bytes are handled correctly: as a single character, not two 2-byte characters.
    2. Unicode properties can be used in the search: \p{…}.

    With Unicode properties we can look for words in given languages, special characters (quotes, currencies) and so on.

    Nullish coalescing operator ‘??'''

    [recent browser="new"]

    The nullish coalescing operator is written as two question marks ??.

    As it treats null and undefined similarly, we'll use a special term here, in this article. We'll say that an expression is "defined" when it's neither null nor undefined.

    The result of a ?? b is: - if a is defined, then a, - if a isn't defined, then b.

    In other words, ?? returns the first argument if it's not null/undefined. Otherwise, the second one.

    The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two.

    We can rewrite result = a ?? b using the operators that we already know, like this:

    Now it should be absolutely clear what ?? does. Let's see where it helps.

    The common use case for ?? is to provide a default value for a potentially undefined variable.

    For example, here we show user if defined, otherwise Anonymous:

    run let user;

    alert(user ?? "Anonymous"); // Anonymous (user not defined)

    Here's the example with user assigned to a name:

    run let user = "John";

    alert(user ?? "Anonymous"); // John (user defined)

    We can also use a sequence of ?? to select the first value from a list that isn't null/undefined.

    Let's say we have a user's data in variables firstName, lastName or nickName. All of them may be not defined, if the user decided not to enter a value.

    We'd like to display the user name using one of these variables, or show "Anonymous" if all of them aren't defined.

    Let's use the ?? operator for that:

    run let firstName = null; let lastName = null; let nickName = "Supercoder";

    // shows the first defined value: ! alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder /!

    Comparison with ||

    The OR || operator can be used in the same way as ??, as it was described in the previous chapter.

    For example, in the code above we could replace ?? with || and still get the same result:

    run let firstName = null; let lastName = null; let nickName = "Supercoder";

    // shows the first truthy value: ! alert(firstName || lastName || nickName || "Anonymous"); // Supercoder /!

    Historically, the OR || operator was there first. It exists since the beginning of JavaScript, so developers were using it for such purposes for a long time.

    On the other hand, the nullish coalescing operator ?? was added to JavaScript only recently, and the reason for that was that people weren't quite happy with ||.

    The important difference between them is that: - || returns the first truthy value. - ?? returns the first defined value.

    In other words, || doesn't distinguish between false, 0, an empty string "" and null/undefined. They are all the same - falsy values. If any of these is the first argument of ||, then we'll get the second argument as the result.

    In practice though, we may want to use default value only when the variable is null/undefined. That is, when the value is really unknown/not set.

    For example, consider this:

    run let height = 0;

    alert(height || 100); // 100 alert(height ?? 100); // 0

    • The height || 100 checks height for being a falsy value, and it's 0, falsy indeed.
      • so the result of || is the second argument, 100.
    • The height ?? 100 checks height for being null/undefined, and it's not,
      • so the result is height "as is", that is 0.

    In practice, the zero height is often a valid value, that shouldn't be replaced with the default. So ?? does just the right thing.

    Precedence

    The precedence of the ?? operator is about the same as ||, just a bit lower. It equals 5 in the MDN table, while || is 6.

    That means that, just like ||, the nullish coalescing operator ?? is evaluated before = and ?, but after most other operations, such as +, *.

    So if we'd like to choose a value with ?? in an expression with other operators, consider adding parentheses:

    run let height = null; let width = null;

    // important: use parentheses let area = (height ?? 100) * (width ?? 50);

    alert(area); // 5000

    Otherwise, if we omit parentheses, then as * has the higher precedence than ??, it would execute first, leading to incorrect results.

    Using ?? with && or ||

    Due to safety reasons, JavaScript forbids using ?? together with && and || operators, unless the precedence is explicitly specified with parentheses.

    The code below triggers a syntax error:

    js run let x = 1 && 2 ?? 3; // Syntax error

    The limitation is surely debatable, it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch from || to ??.

    Use explicit parentheses to work around it:

    run ! let x = (1 && 2) ?? 3; // Works /!

    alert(x); // 2

    Summary

    • The nullish coalescing operator ?? provides a short way to choose the first "defined" value from a list.

      It's used to assign default values to variables:

    • The operator ?? has a very low precedence, only a bit higher than ? and =, so consider adding parentheses when using it in an expression.
    • It's forbidden to use it with || or && without explicit parentheses.

    Anchors: string start ^ and end $

    The caret pattern:^ and dollar pattern:$ characters have special meaning in a regexp. They are called "anchors".

    The caret pattern:^ matches at the beginning of the text, and the dollar pattern:$ - at the end.

    For instance, let's test if the text starts with Mary:

    js run let str1 = "Mary had a little lamb"; alert( /^Mary/.test(str1) ); // true

    The pattern pattern:^Mary means: "string start and then Mary".

    Similar to this, we can test if the string ends with snow using pattern:snow$:

    js run let str1 = "it's fleece was white as snow"; alert( /snow$/.test(str1) ); // true

    In these particular cases we could use string methods startsWith/endsWith instead. Regular expressions should be used for more complex tests.

    Testing for a full match

    Both anchors together pattern:^...$ are often used to test whether or not a string fully matches the pattern. For instance, to check if the user input is in the right format.

    Let's check whether or not a string is a time in 12:34 format. That is: two digits, then a colon, and then another two digits.

    In regular expressions language that's pattern:\d\d:\d\d:

    run let goodInput = "12:34"; let badInput = "12:345";

    let regexp = /^\d\d$/; alert( regexp.test(goodInput) ); // true alert( regexp.test(badInput) ); // false

    Here the match for pattern:\d\d:\d\d must start exactly after the beginning of the text pattern:^, and the end pattern:$ must immediately follow.

    The whole string must be exactly in this format. If there's any deviation or an extra character, the result is false.

    Anchors behave differently if flag pattern:m is present. We'll see that in the next article.

    `` smart header="Anchors have \"zero width\"" Anchorspattern:^ andpattern:$` are tests. They have zero width.

    In other words, they do not match a character, but rather force the regexp engine to check the condition (text start/end).

    Multiline mode of anchors ^ $, flag "m"

    The multiline mode is enabled by the flag pattern:m.

    It only affects the behavior of pattern:^ and pattern:$.

    In the multiline mode they match not only at the beginning and the end of the string, but also at start/end of line.

    Searching at line start ^

    In the example below the text has multiple lines. The pattern pattern:/^\d/gm takes a digit from the beginning of each line:

    `` js run let str =1st place: Winnie 2nd place: Piglet 3rd place: Eeyore`;

    ! alert( str.match(/^gm) ); // 1, 2, 3 /!

    Without the flag pattern:m only the first digit is matched:

    `` js run let str =1st place: Winnie 2nd place: Piglet 3rd place: Eeyore`;

    ! alert( str.match(/^g) ); // 1 /!

    That's because by default a caret pattern:^ only matches at the beginning of the text, and in the multiline mode - at the start of any line.

                                                                                                                          "Start of a line" formally means "immediately after a line break": the test  `pattern:^` in multiline mode matches at all positions preceded by a newline character `\n`.
    And at the text start.
                                                                                                                        

    Searching at line end $

    The dollar sign pattern:$ behaves similarly.

    The regular expression pattern:\d$ finds the last digit in every line

    `` js run let str =Winnie: 1 Piglet: 2 Eeyore: 3`;

    alert( str.match(/\d$/gm) ); // 1,2,3

    Without the flag pattern:m, the dollar pattern:$ would only match the end of the whole text, so only the very last digit would be found.

                                                                                                                          "End of a line" formally means "immediately before a line break": the test  `pattern:$` in multiline mode matches at all positions succeeded by a newline character `\n`.
    And at the text end.
                                                                                                                        

    Searching for instead of ^ $

    To find a newline, we can use not only anchors pattern:^ and pattern:$, but also the newline character \n.

    What's the difference? Let's see an example.

    Here we search for pattern:\d\n instead of pattern:\d$:

    `` js run let str =Winnie: 1 Piglet: 2 Eeyore: 3`;

    alert( str.match(//gm) ); // 1,2

    As we can see, there are 2 matches instead of 3.

    That's because there's no newline after subject:3 (there's text end though, so it matches pattern:$).

    Another difference: now every match includes a newline character match:\n. Unlike the anchors pattern:^ pattern:$, that only test the condition (start/end of a line), \n is a character, so it becomes a part of the result.

    So, a \n in the pattern is used when we need newline characters in the result, while anchors are used to find something at the beginning/end of a line.

    Word boundary: word boundary pattern:\b is a test, just like pattern:^ and pattern:$.

    When the regexp engine (program module that implements searching for regexps) comes across pattern:\b, it checks that the position in the string is a word boundary.

    There are three different positions that qualify as word boundaries:

    • At string start, if the first string character is a word character pattern:\w.
    • Between two characters in the string, where one is a word character pattern:\w and the other is not.
    • At string end, if the last string character is a word character pattern:\w.

    For instance, regexp pattern:\bJava\b will be found in subject:Hello, Java!, where subject:Java is a standalone word, but not in subject:Hello, JavaScript!.

    js run alert( "Hello, Java!".match(/\bJava\b/) ); // Java alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null

    In the string subject:Hello, Java! following positions correspond to pattern:\b:

    So, it matches the pattern pattern:\bHello\b, because:

    1. At the beginning of the string matches the first test pattern:\b.
    2. Then matches the word pattern:Hello.
    3. Then the test pattern:\b matches again, as we're between subject:o and a comma.

    So the pattern pattern:\bHello\b would match, but not pattern:\bHell\b (because there's no word boundary after l) and not Java!\b (because the exclamation sign is not a wordly character pattern:\w, so there's no word boundary after it).

    js run alert( "Hello, Java!".match(/\bHello\b/) ); // Hello alert( "Hello, Java!".match(/\bJava\b/) ); // Java alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match) alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match)

    We can use pattern:\b not only with words, but with digits as well.

    For example, the pattern pattern:\b\d\d\b looks for standalone 2-digit numbers. In other words, it looks for 2-digit numbers that are surrounded by characters different from pattern:\w, such as spaces or punctuation (or text start/end).

    js run alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 alert( "12,34,56".match(/\b\d\d\b/g) ); // 12,34,56

    `` warn header="Word boundarypattern: doesn't work for non-latin alphabets" The word boundary test pattern:\b checks that there should be pattern:\w on the one side from the position and "not pattern:\w" - on the other side.

    But pattern:\w means a latin letter a-z (or a digit or an underscore), so the test doesn't work for other characters, e.g. cyrillic letters or hieroglyphs.

    Escaping, special characters

    As we've seen, a backslash pattern:\ is used to denote character classes, e.g. pattern:\d. So it's a special character in regexps (just like in regular strings).

    There are other special characters as well, that have special meaning in a regexp. They are used to do more powerful searches. Here's a full list of them: pattern:[ \ ^ $ . | ? * + ( ).

    Don't try to remember the list - soon we'll deal with each of them separately and you'll know them by heart automatically.

    Escaping

    Let's say we want to find literally a dot. Not "any character", but just a dot.

    To use a special character as a regular one, prepend it with a backslash: pattern:\..

    That's also called "escaping a character".

    For example: js run alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1 (match!) alert( "Chapter 511".match(/\d\.\d/) ); // null (looking for a real dot \.)

    Parentheses are also special characters, so if we want them, we should use pattern:\(. The example below looks for a string "g()":

    js run alert( "function g()".match(/g\(\)/) ); // "g()"

    If we're looking for a backslash \, it's a special character in both regular strings and regexps, so we should double it.

    js run alert( "1\\2".match(/\\/) ); // '\'

    A slash

    A slash symbol '/' is not a special character, but in JavaScript it is used to open and close the regexp: pattern:/...pattern.../, so we should escape it too.

    Here's what a search for a slash '/' looks like:

    js run alert( "/".match(/\//) ); // '/'

    On the other hand, if we're not using pattern:/.../, but create a regexp using new RegExp, then we don't need to escape it:

    js run alert( "/".match(new RegExp("/")) ); // finds /

    new RegExp

    If we are creating a regular expression with new RegExp, then we don't have to escape /, but need to do some other escaping.

    For instance, consider this:

    run let regexp = new RegExp(");

    alert( "Chapter 5.1".match(regexp) ); // null

    The similar search in one of previous examples worked with pattern:/\d\.\d/, but new RegExp("\d\.\d") doesn't work, why?

    The reason is that backslashes are "consumed" by a string. As we may recall, regular strings have their own special characters, such as \n, and a backslash is used for escaping.

    Here's how " is preceived:

    js run alert("\d\.\d"); // d.d

    String quotes "consume" backslashes and interpret them on their own, for instance:

    • \n - becomes a newline character,
    • \u1234 - becomes the Unicode character with such code,
    • …And when there's no special meaning: like pattern:\d or \z, then the backslash is simply removed.

    So new RegExp gets a string without backslashes. That's why the search doesn't work!

    To fix it, we need to double backslashes, because string quotes turn \\ into \:

    run ! let regStr = "\d\.\d"; /! alert(regStr); // correct now)

    let regexp = new RegExp(regStr);

    alert( "Chapter 5.1".match(regexp) ); // 5.1

    Summary

    • To search for special characters pattern:[ \ ^ $ . | ? * + ( ) literally, we need to prepend them with a backslash \ ("escape them").
    • We also need to escape / if we're inside pattern:/.../ (but not inside new RegExp).
    • When passing a string to new RegExp, we need to double backslashes \\, cause string quotes consume one of them.

    Sets and ranges […]

    Several characters or character classes inside square brackets […] mean to "search for any character among given".

    Sets

    For instance, pattern:[eao] means any of the 3 characters: 'a', 'e', or 'o'.

    That's called a set. Sets can be used in a regexp along with regular characters:

    js run // find [t or m], and then "op" alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"

    Please note that although there are multiple characters in the set, they correspond to exactly one character in the match.

    So the example below gives no matches:

    js run // find "V", then [o or i], then "la" alert( "Voila".match(/V[oi]la/) ); // null, no matches

    The pattern searches for:

    • pattern:V,
    • then one of the letters pattern:[oi],
    • then pattern:la.

    So there would be a match for match:Vola or match:Vila.

    Ranges

    Square brackets may also contain character ranges.

    For instance, pattern:[a-z] is a character in range from a to z, and pattern:[0-5] is a digit from 0 to 5.

    In the example below we're searching for "x" followed by two digits or letters from A to F:

    js run alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF

    Here pattern:[0-9A-F] has two ranges: it searches for a character that is either a digit from 0 to 9 or a letter from A to F.

    If we'd like to look for lowercase letters as well, we can add the range a-f: pattern:[0-9A-Fa-f]. Or add the flag pattern:i.

    We can also use character classes inside […].

    For instance, if we'd like to look for a wordly character pattern:\w or a hyphen pattern:-, then the set is pattern:[\w-].

    Combining multiple classes is also possible, e.g. pattern:[\s\d] means "a space character or a digit".

    smart header="Character classes are shorthands for certain character sets" For instance:

    • ** - is the same as pattern:[0-9],
    • *** - is the same as pattern:[a-zA-Z0-9_],
    • *** - is the same as pattern:[\t\n\v\f\r ], plus few other rare Unicode space characters.

    Example: multi-language

    As the character class pattern:\w is a shorthand for pattern:[a-zA-Z0-9_], it can't find Chinese hieroglyphs, Cyrillic letters, etc.

    We can write a more universal pattern, that looks for wordly characters in any language. That's easy with Unicode properties: pattern:[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}].

    Let's decipher it. Similar to pattern:\w, we're making a set of our own that includes characters with following Unicode properties:

    • Alphabetic ( Alpha) - for letters,
    • Mark ( M) - for accents,
    • Decimal_Number ( Nd) - for digits,
    • Connector_Punctuation ( Pc) - for the underscore '_' and similar characters,
    • Join_Control ( Join_C) - two special codes 200c and 200d, used in ligatures, e.g. in Arabic.

    An example of use:

    run let regexp = /[]/gu;

    let str = Hi 你好 12;

    // finds all letters and digits: alert( str.match(regexp) ); // H,i,你,好,1,2

    Of course, we can edit this pattern: add Unicode properties or remove them. Unicode properties are covered in more details in the article info:regexp-unicode.

    `` warn header="Unicode properties aren't supported in IE" Unicode propertiespattern:p{…}` are not implemented in IE. If we really need them, we can use library XRegExp.

    Or just use ranges of characters in a language that interests us, e.g. pattern:[а-я] for Cyrillic letters.

    Excluding ranges

    Besides normal ranges, there are "excluding" ranges that look like pattern:[^…].

    They are denoted by a caret character ^ at the start and match any character except the given ones.

    For instance:

    • pattern:[^aeyo] - any character except 'a', 'e', 'y' or 'o'.
    • pattern:[^0-9] - any character except a digit, the same as pattern:\D.
    • pattern:[^\s] - any non-space character, same as \S.

    The example below looks for any characters except letters, digits and spaces:

    js run alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .

    Escaping in […]

    Usually when we want to find exactly a special character, we need to escape it like pattern:\.. And if we need a backslash, then we use pattern:\\, and so on.

    In square brackets we can use the vast majority of special characters without escaping:

    • Symbols pattern:. + ( ) never need escaping.
    • A hyphen pattern:- is not escaped in the beginning or the end (where it does not define a range).
    • A caret pattern:^ is only escaped in the beginning (where it means exclusion).
    • The closing square bracket pattern:] is always escaped (if we need to look for that symbol).

    In other words, all special characters are allowed without escaping, except when they mean something for square brackets.

    A dot . inside square brackets means just a dot. The pattern pattern:[.,] would look for one of characters: either a dot or a comma.

    In the example below the regexp pattern:[-().^+] looks for one of the characters -().^+:

    run // No need to escape let regexp = /[-().^+]/g;

    alert( "1 + 2 - 3".match(regexp) ); // Matches +, -

    …But if you decide to escape them "just in case", then there would be no harm:

    run // Escaped everything let regexp = /[-().^+]/g;

    alert( "1 + 2 - 3".match(regexp) ); // also works: +, -

    Ranges and flag "u"

    If there are surrogate pairs in the set, flag pattern:u is required for them to work correctly.

    For instance, let's look for pattern:[𝒳𝒴] in the string subject:𝒳:

    js run alert( '𝒳'.match(/[𝒳𝒴]/) ); // shows a strange character, like [?] // (the search was performed incorrectly, half-character returned)

    The result is incorrect, because by default regular expressions "don't know" about surrogate pairs.

    The regular expression engine thinks that [𝒳𝒴] - are not two, but four characters: 1. left half of 𝒳 (1), 2. right half of 𝒳 (2), 3. left half of 𝒴 (3), 4. right half of 𝒴 (4).

    We can see their codes like this:

    js run for(let i=0; i<'𝒳𝒴'.length; i++) { alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500 };

    So, the example above finds and shows the left half of 𝒳.

    If we add flag pattern:u, then the behavior will be correct:

    js run alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳

    The similar situation occurs when looking for a range, such as [𝒳-𝒴].

    If we forget to add flag pattern:u, there will be an error:

    js run '𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression

    The reason is that without flag pattern:u surrogate pairs are perceived as two characters, so [𝒳-𝒴] is interpreted as [<55349><56499>-<55349><56500>] (every surrogate pair is replaced with its codes). Now it's easy to see that the range 56499-55349 is invalid: its starting code 56499 is greater than the end 55349. That's the formal reason for the error.

    With the flag pattern:u the pattern works correctly:

    js run // look for characters from 𝒳 to 𝒵 alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴

    Quantifiers +, *, ? and {n}

    Let's say we have a string like +7(903)-123-45-67 and want to find all numbers in it. But unlike before, we are interested not in single digits, but full numbers: 7, 903, 123, 45, 67.

    A number is a sequence of 1 or more digits pattern:\d. To mark how many we need, we can append a quantifier.

    Quantity {n}

    The simplest quantifier is a number in curly braces: pattern:{n}.

    A quantifier is appended to a character (or a character class, or a [...] set etc) and specifies how many we need.

    It has a few advanced forms, let's see examples:

    The exact count: pattern:{5}

    pattern:\d{5} denotes exactly 5 digits, the same as pattern:\d\d\d\d\d.

    The example below looks for a 5-digit number:

    js run alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345"

    We can add \b to exclude longer numbers: pattern:\b\d{5}\b.

    The range: pattern:{3,5}, match 3-5 times

    To find numbers from 3 to 5 digits we can put the limits into curly braces: pattern:\d{3,5}

    js run alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234"

    We can omit the upper limit.

    Then a regexp pattern:\d{3,} looks for sequences of digits of length 3 or more:

    js run alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) ); // "345678"

    Let's return to the string +7(903)-123-45-67.

    A number is a sequence of one or more digits in a row. So the regexp is pattern:\d{1,}:

    run let str = "+7(903)-123-45-67";

    let numbers = str.match(//g);

    alert(numbers); // 7,903,123,45,67

    Shorthands

    There are shorthands for most used quantifiers:

    pattern:+

    Means "one or more", the same as pattern:{1,}.

    For instance, pattern:\d+ looks for numbers:

    run let str = "+7(903)-123-45-67";

    alert( str.match(//g) ); // 7,903,123,45,67

    pattern:?

    Means "zero or one", the same as pattern:{0,1}. In other words, it makes the symbol optional.

    For instance, the pattern pattern:ou?r looks for match:o followed by zero or one match:u, and then match:r.

    So, pattern:colou?r finds both match:color and match:colour:

    run let str = "Should I write color or colour?";

    alert( str.match(/colou?r/g) ); // color, colour

    pattern:*

    Means "zero or more", the same as pattern:{0,}. That is, the character may repeat any times or be absent.

    For example, pattern:\d0* looks for a digit followed by any number of zeroes (may be many or none):

    js run alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1

    Compare it with pattern:+ (one or more):

    js run alert( "100 10 1".match(/\d0+/g) ); // 100, 10 // 1 not matched, as 0+ requires at least one zero

    More examples

    Quantifiers are used very often. They serve as the main "building block" of complex regular expressions, so let's see more examples.

    Regexp for decimal fractions (a number with a floating point): pattern:\d+\.\d+

    In action: js run alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345

    Regexp for an "opening HTML-tag without attributes", such as <span> or <p>.

    1. The simplest one: pattern:/<[a-z]+>/i

      js run alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>

      The regexp looks for character pattern:'<' followed by one or more Latin letters, and then pattern:'>'.

    2. Improved: pattern:/<[a-z][a-z0-9]*>/i

      According to the standard, HTML tag name may have a digit at any position except the first one, like <h1>.

      js run alert( "<h1>Hi!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1>

    Regexp "opening or closing HTML-tag without attributes": pattern:/<\/?[a-z][a-z0-9]*>/i

    We added an optional slash pattern:/? near the beginning of the pattern. Had to escape it with a backslash, otherwise JavaScript would think it is the pattern end.

    js run alert( "<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>

    smart header="To make a regexp more precise, we often need make it more complex" We can see one common rule in these examples: the more precise is the regular expression - the longer and more complex it is.

    For instance, for HTML tags we could use a simpler regexp: pattern:<\w+>. But as HTML has stricter restrictions for a tag name, pattern:<[a-z][a-z0-9]*> is more reliable.

    Can we use pattern:<\w+> or we need pattern:<[a-z][a-z0-9]*>?

    In real life both variants are acceptable. Depends on how tolerant we can be to "extra" matches and whether it's difficult or not to remove them from the result by other means.

    Greedy and lazy quantifiers

    Quantifiers are very simple from the first sight, but in fact they can be tricky.

    We should understand how the search works very well if we plan to look for something more complex than pattern:/\d+/.

    Let's take the following task as an example.

    We have a text and need to replace all quotes "..." with guillemet marks: «...». They are preferred for typography in many countries.

    For instance: "Hello, world" should become «Hello, world». There exist other quotes, such as „Witam, świat!" (Polish) or 「你好,世界」 (Chinese), but for our task let's choose «...».

    The first thing to do is to locate quoted strings, and then we can replace them.

    A regular expression like pattern:/".+"/g (a quote, then something, then the other quote) may seem like a good fit, but it isn't!

    Let's try it:

    run let regexp = /".+"/g;

    let str = ‘a "witch" and her "broom" is one';

    alert( str.match(regexp) ); // "witch" and her "broom"

    …We can see that it works not as intended!

    Instead of finding two matches match:"witch" and match:"broom", it finds one: match:"witch" and her "broom".

    That can be described as "greediness is the cause of all evil".

    To find a match, the regular expression engine uses the following algorithm:

    • For every position in the string
      • Try to match the pattern at that position.
      • If there's no match, go to the next position.

    These common words do not make it obvious why the regexp fails, so let's elaborate how the search works for the pattern pattern:".+".

    1. The first pattern character is a quote pattern:".

      The regular expression engine tries to find it at the zero position of the source string subject:a "witch" and her "broom" is one, but there's subject:a there, so there's immediately no match.

      Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, fails again, and finally finds the quote at the 3rd position:

    2. The quote is detected, and then the engine tries to find a match for the rest of the pattern. It tries to see if the rest of the subject string conforms to pattern:.+".

      In our case the next pattern character is pattern:. (a dot). It denotes "any character except a newline", so the next string letter match:'w' fits:

    3. Then the dot repeats because of the quantifier pattern:.+. The regular expression engine adds to the match one character after another.

      …Until when? All characters match the dot, so it only stops when it reaches the end of the string:

    4. Now the engine finished repeating pattern:.+ and tries to find the next character of the pattern. It's the quote pattern:". But there's a problem: the string has finished, there are no more characters!

      The regular expression engine understands that it took too many pattern:.+ and starts to backtrack.

      In other words, it shortens the match for the quantifier by one character:

      Now it assumes that pattern:.+ ends one character before the string end and tries to match the rest of the pattern from that position.

      If there were a quote there, then the search would end, but the last character is subject:'e', so there's no match.

    5. …So the engine decreases the number of repetitions of pattern:.+ by one more character:

      The quote pattern:'"' does not match subject:'n'.

    6. The engine keep backtracking: it decreases the count of repetition for pattern:'.' until the rest of the pattern (in our case pattern:'"') matches:

    7. The match is complete.

    8. So the first match is match:"witch" and her "broom". If the regular expression has flag pattern:g, then the search will continue from where the first match ends. There are no more quotes in the rest of the string subject:is one, so no more results.

    That's probably not what we expected, but that's how it works.

    In the greedy mode (by default) a quantified character is repeated as many times as possible.

    The regexp engine adds to the match as many characters as it can for pattern:.+, and then shortens that one by one, if the rest of the pattern doesn't match.

    For our task we want another thing. That's where a lazy mode can help.

    Lazy mode

    The lazy mode of quantifiers is an opposite to the greedy mode. It means: "repeat minimal number of times".

    We can enable it by putting a question mark pattern:'?' after the quantifier, so that it becomes pattern:*? or pattern:+? or even pattern:?? for pattern:'?'.

    To make things clear: usually a question mark pattern:? is a quantifier by itself (zero or one), but if added after another quantifier (or even itself) it gets another meaning - it switches the matching mode from greedy to lazy.

    The regexp pattern:/".+?"/g works as intended: it finds match:"witch" and match:"broom":

    run let regexp = /".+?"/g;

    let str = ‘a "witch" and her "broom" is one';

    alert( str.match(regexp) ); // "witch", "broom"

    To clearly understand the change, let's trace the search step by step.

    1. The first step is the same: it finds the pattern start pattern:'"' at the 3rd position:

    2. The next step is also similar: the engine finds a match for the dot pattern:'.':

    3. And now the search goes differently. Because we have a lazy mode for pattern:+?, the engine doesn't try to match a dot one more time, but stops and tries to match the rest of the pattern pattern:'"' right now:

      If there were a quote there, then the search would end, but there's 'i', so there's no match.
    4. Then the regular expression engine increases the number of repetitions for the dot and tries one more time:

      Failure again. Then the number of repetitions is increased again and again…
    5. …Till the match for the rest of the pattern is found:

    6. The next search starts from the end of the current match and yield one more result:

    In this example we saw how the lazy mode works for pattern:+?. Quantifiers pattern:*? and pattern:?? work the similar way - the regexp engine increases the number of repetitions only if the rest of the pattern can't match on the given position.

    Laziness is only enabled for the quantifier with ?.

    Other quantifiers remain greedy.

    For instance:

    js run alert( "123 456".match(/\d+ \d+?/) ); // 123 4

    1. The pattern pattern:\d+ tries to match as many digits as it can (greedy mode), so it finds match:123 and stops, because the next character is a space pattern:' '.
    2. Then there's a space in the pattern, it matches.
    3. Then there's pattern:\d+?. The quantifier is in lazy mode, so it finds one digit match:4 and tries to check if the rest of the pattern matches from there.

      …But there's nothing in the pattern after pattern:\d+?.

      The lazy mode doesn't repeat anything without a need. The pattern finished, so we're done. We have a match match:123 4.

    smart header="Optimizations" Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit differently from the described algorithm.

    But to understand how regular expressions work and to build regular expressions, we don't need to know about that. They are only used internally to optimize things.

    Complex regular expressions are hard to optimize, so the search may work exactly as described as well.

    Alternative approach

    With regexps, there's often more than one way to do the same thing.

    In our case we can find quoted strings without lazy mode using the regexp pattern:"[^"]+":

    run let regexp = /"[^"]+"/g;

    let str = ‘a "witch" and her "broom" is one';

    alert( str.match(regexp) ); // "witch", "broom"

    The regexp pattern:"[^"]+" gives correct results, because it looks for a quote pattern:'"' followed by one or more non-quotes pattern:[^"], and then the closing quote.

    When the regexp engine looks for pattern:[^"]+ it stops the repetitions when it meets the closing quote, and we're done.

    Please note, that this logic does not replace lazy quantifiers!

    It is just different. There are times when we need one or another.

    Let's see an example where lazy quantifiers fail and this variant works right.

    For instance, we want to find links of the form <a href="..." class="doc">, with any href.

    Which regular expression to use?

    The first idea might be: pattern:/<a href=".*" class="doc">/g.

    Let's check it: run let str = ‘… …'; let regexp = / /g;

    // Works! alert( str.match(regexp) ); //

    It worked. But let's see what happens if there are many links in the text?

    run let str = ‘… …'; let regexp = / /g;

    // Whoops! Two links in one match! alert( str.match(regexp) ); //

    Now the result is wrong for the same reason as our "witches" example. The quantifier pattern:.* took too many characters.

    The match looks like this:

    Let's modify the pattern by making the quantifier pattern:.*? lazy:

    run let str = ‘… …'; let regexp = / /g;

    // Works! alert( str.match(regexp) ); // ,

    Now it seems to work, there are two matches:

    …But let's test it on one more text input:

    run let str = '…

    …'; let regexp = / /g;

    // Wrong match! alert( str.match(regexp) ); //

    Now it fails. The match includes not just a link, but also a lot of text after it, including <p...>.

    Why?

    That's what's going on:

    1. First the regexp finds a link start match:<a href=".
    2. Then it looks for pattern:.*?: takes one character (lazily!), check if there's a match for pattern:" class="doc"> (none).
    3. Then takes another character into pattern:.*?, and so on… until it finally reaches match:" class="doc">.

    But the problem is: that's already beyond the link <a...>, in another tag <p>. Not what we want.

    Here's the picture of the match aligned with the text:

    So, we need the pattern to look for <a href="...something..." class="doc">, but both greedy and lazy variants have problems.

    The correct variant can be: pattern:href="[^"]*". It will take all characters inside the href attribute till the nearest quote, just what we need.

    A working example:

    run let str1 = '…

    …‘; let str2 ='… …'; let regexp = /<a href="[^"]*" class="doc">/g;

    // Works! alert( str1.match(regexp) ); // null, no matches, that's correct alert( str2.match(regexp) ); // ,

    Summary

    Quantifiers have two modes of work:

    Greedy
    By default the regular expression engine tries to repeat the quantified character as many times as possible. For instance, pattern:\d+ consumes all possible digits. When it becomes impossible to consume more (no more digits or string end), then it continues to match the rest of the pattern. If there's no match then it decreases the number of repetitions (backtracks) and tries again.
    Lazy
    Enabled by the question mark pattern:? after the quantifier. The regexp engine tries to match the rest of the pattern before each repetition of the quantified character.

    As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions, as in the pattern pattern:"[^"]+".

    Capturing groups

    A part of a pattern can be enclosed in parentheses pattern:(...). This is called a "capturing group".

    That has two effects:

    1. It allows to get a part of the match as a separate item in the result array.
    2. If we put a quantifier after the parentheses, it applies to the parentheses as a whole.

    Examples

    Let's see how parentheses work in examples.

    Example: gogogo

    Without parentheses, the pattern pattern:go+ means subject:g character, followed by subject:o repeated one or more times. For instance, match:goooo or match:gooooooooo.

    Parentheses group characters together, so pattern:(go)+ means match:go, match:gogo, match:gogogo and so on.

    js run alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo"

    Example: domain

    Let's make something more complex - a regular expression to search for a website domain.

    For example:

                                                                                                                                                                          mail.com
    users.mail.com
    smith.users.mail.com
                                                                                                                                                                        

    As we can see, a domain consists of repeated words, a dot after each one except the last one.

    In regular expressions that's pattern:(\w+\.)+\w+:

    run let regexp = /(+.)++/g;

    alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com

    The search works, but the pattern can't match a domain with a hyphen, e.g. my-site.com, because the hyphen does not belong to class pattern:\w.

    We can fix it by replacing pattern:\w with pattern:[\w-] in every word except the last one: pattern:([\w-]+\.)+\w+.

    Example: email

    The previous example can be extended. We can create a regular expression for emails based on it.

    The email format is: name@domain. Any word can be the name, hyphens and dots are allowed. In regular expressions that's pattern:[-.\w]+.

    The pattern:

    run let regexp = /[-.]+@([-]+.)+[-]+/g;

    alert("my@mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk

    That regexp is not perfect, but mostly works and helps to fix accidental mistypes. The only truly reliable check for an email can only be done by sending a letter.

    Parentheses contents in the match

    Parentheses are numbered from left to right. The search engine memorizes the content matched by each of them and allows to get it in the result.

    The method str.match(regexp), if regexp has no flag g, looks for the first match and returns it as an array:

    1. At index 0: the full match.
    2. At index 1: the contents of the first parentheses.
    3. At index 2: the contents of the second parentheses.
    4. …and so on…

    For instance, we'd like to find HTML tags pattern:<.*?>, and process them. It would be convenient to have tag content (what's inside the angles), in a separate variable.

    Let's wrap the inner content into parentheses, like this: pattern:<(.*?)>.

    Now we'll get both the tag as a whole match:<h1> and its contents match:h1 in the resulting array:

    run let str = '''

    Hello, world!

    ''';

    let tag = str.match(/<(.*?)>/);

    alert( tag[0] ); //

    alert( tag[1] ); // h1

    Nested groups

    Parentheses can be nested. In this case the numbering also goes from left to right.

    For instance, when searching a tag in subject:<span class="my"> we may be interested in:

    1. The tag content as a whole: match:span class="my".
    2. The tag name: match:span.
    3. The tag attributes: match:class="my".

    Let's add parentheses for them: pattern:<(([a-z]+)\s*([^>]*))>.

    Here's how they are numbered (left to right, by the opening paren):

    In action:

    run let str = ‘ ''';

    let regexp = /<(([a-z]+)([^>]*))>/;

    let result = str.match(regexp); alert(result[0]); // alert(result[1]); // span class="my" alert(result[2]); // span alert(result[3]); // class="my"

    The zero index of result always holds the full match.

    Then groups, numbered from left to right by an opening paren. The first group is returned as result[1]. Here it encloses the whole tag content.

    Then in result[2] goes the group from the second opening paren pattern:([a-z]+) - tag name, then in result[3] the tag: pattern:([^>]*).

    The contents of every group in the string:

    Optional groups

    Even if a group is optional and doesn't exist in the match (e.g. has the quantifier pattern:(...)?), the corresponding result array item is present and equals undefined.

    For instance, let's consider the regexp pattern:a(z)?(c)?. It looks for "a" optionally followed by "z" optionally followed by "c".

    If we run it on the string with a single letter subject:a, then the result is:

    run let match = ‘a'.match(/a(z)?(c)?/);

    alert( match.length ); // 3 alert( match[0] ); // a (whole match) alert( match[1] ); // undefined alert( match[2] ); // undefined

    The array has the length of 3, but all groups are empty.

    And here's a more complex match for the string subject:ac:

    run let match = ‘ac'.match(/a(z)?(c)?/)

    alert( match.length ); // 3 alert( match[0] ); // ac (whole match) alert( match[1] ); // undefined, because there's nothing for (z)? alert( match[2] ); // c

    The array length is permanent: 3. But there's nothing for the group pattern:(z)?, so the result is ["ac", undefined, "c"].

    Searching for all matches with groups: matchAll

    `` warn header="matchAll is a new method, polyfill may be needed" The methodmatchAll` is not supported in old browsers.

    A polyfill may be required, such as https://github.com/ljharb/String.prototype.matchAll.

    When we search for all matches (flag pattern:g), the match method does not return contents for groups.

    For example, let's find all tags in a string:

    run let str = '''

    ''';

    let tags = str.match(/<(.*?)>/g);

    alert( tags ); //

    ,

    The result is an array of matches, but without details about each of them. But in practice we usually need contents of capturing groups in the result.

    To get them, we should search using the method undefinedundefinedstr.matchAll(regexp).

    It was added to JavaScript language long after undefinedundefinedmatch, as its "new and improved version".

    Just like undefinedundefinedmatch, it looks for matches, but there are 3 differences:

      undefinedundefined
    1. It returns not an array, but an iterable object.
    2. undefinedundefined
    3. When the flag undefinedundefinedpattern:g is present, it returns every match as an array with groups.undefinedundefined
    4. undefinedundefined
    5. If there are no matches, it returns not undefinedundefinednull, but an empty iterable object.undefinedundefined

    For instance:

    run let results = '''

    undefinedundefined

    undefinedundefined

    '''.matchAll(/<(.*?)>/gi);

    undefinedundefined

    // results - is not an array, but an iterable object alert(results); // [object RegExp String Iterator]

    undefinedundefined

    alert(results[0]); // undefined (*)

    undefinedundefined

    results = Array.from(results); // let's turn it into array

    alert(results[0]); // undefinedundefined

    ,h1 (1st tag) alert(results[1]); // undefinedundefined

    undefinedundefined

    ,h2 (2nd tag)

    undefinedundefined

    As we can see, the first difference is very important, as demonstrated in the line undefinedundefined(*). We can't get the match as undefinedundefinedresults[0], because that object isn't pseudoarray. We can turn it into a real undefinedundefinedArray using undefinedundefinedArray.from. There are more details about pseudoarrays and iterables in the article undefinedundefinedinfo:iterable.undefinedundefined

    undefinedundefined

    There's no need in undefinedundefinedArray.from if we're looping over results:undefinedundefined

    run let results = ''' undefinedundefined

    undefinedundefined

    undefinedundefined

    '''.matchAll(/<(.*?)>/gi);

    for(let result of results) { alert(result); // first alert: undefinedundefined

    ,h1 // second: undefinedundefined

    undefinedundefined

    ,h2 }

    undefinedundefined

    …Or using destructuring:

    undefinedundefinedundefinedundefined

    Every match, returned by undefinedundefinedmatchAll, has the same format as returned by undefinedundefinedmatch without flag undefinedundefinedpattern:g: it's an array with additional properties undefinedundefinedindex (match index in the string) and undefinedundefinedinput (source string):undefinedundefined

    run let results = ''' undefinedundefined

    undefinedundefined

    undefinedundefined

    '''.matchAll(/<(.*?)>/gi);

    undefinedundefined

    let [tag1, tag2] = results;

    alert( tag1[0] ); // undefinedundefined

    alert( tag1[1] ); // h1 alert( tag1.index ); // 0 alert( tag1.input ); // undefinedundefined

    undefinedundefined

    undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Why is a result ofmatchAll` an iterable object, not an array?" Why is the method designed like that? The reason is simple - for the optimization.undefinedundefined

    undefinedundefined

    The call to undefinedundefinedmatchAll does not perform the search. Instead, it returns an iterable object, without the results initially. The search is performed each time we iterate over it, e.g. in the loop.undefinedundefined

    undefinedundefined

    So, there will be found as many results as needed, not more.

    undefinedundefined

    E.g. there are potentially 100 matches in the text, but in a undefinedundefinedfor..of loop we found 5 of them, then decided it's enough and made a undefinedundefinedbreak. Then the engine won't spend time finding other 95 matches. undefinedundefined

    undefinedundefined

    Named groups

    undefinedundefined

    Remembering groups by their numbers is hard. For simple patterns it's doable, but for more complex ones counting parentheses is inconvenient. We have a much better option: give names to parentheses.

    undefinedundefined

    That's done by putting undefinedundefinedpattern:?<name> immediately after the opening paren.undefinedundefined

    undefinedundefined

    For example, let's look for a date in the format "year-month-day":

    undefinedundefined

    run undefinedundefined! let dateRegexp = /(?undefinedundefined[0-9]{4})-(?undefinedundefined [0-9]{2})-(?undefinedundefined[0-9]{2})/; undefinedundefined/! let str = "2019-04-30";undefinedundefined

    undefinedundefined

    let groups = str.match(dateRegexp).groups;

    undefinedundefined

    alert(groups.year); // 2019 alert(groups.month); // 04 alert(groups.day); // 30

    undefinedundefined

    As you can see, the groups reside in the undefinedundefined.groups property of the match.undefinedundefined

    undefinedundefined

    To look for all dates, we can add flag undefinedundefinedpattern:g.undefinedundefined

    undefinedundefined

    We'll also need undefinedundefinedmatchAll to obtain full matches, together with groups:undefinedundefined

    undefinedundefined

    run let dateRegexp = /(?undefinedundefined [0-9]{4})-(?undefinedundefined [0-9]{2})-(?undefinedundefined [0-9]{2})/g;undefinedundefined

    undefinedundefined

    let str = "2019-10-30 2020-01-01";

    undefinedundefined

    let results = str.matchAll(dateRegexp);

    undefinedundefined

    for(let result of results) { let {year, month, day} = result.groups;

    undefinedundefined

    alert(undefinedundefined${day}.${month}.${year}); // first alert: 30.10.2019 // second: 01.01.2020 } undefinedundefined

    undefinedundefined

    Capturing groups in replacement

    undefinedundefined

    Method undefinedundefinedstr.replace(regexp, replacement) that replaces all matches with undefinedundefinedregexp in undefinedundefinedstr allows to use parentheses contents in the undefinedundefinedreplacement string. That's done using undefinedundefinedpattern:$n, where undefinedundefinedpattern:n is the group number.undefinedundefined

    undefinedundefined

    For example,

    undefinedundefined

    run let str = "John Bull"; let regexp = /(+) (+)/;

    undefinedundefined

    alert( str.replace(regexp, ‘$2, $1') ); // Bull, John

    undefinedundefined

    For named parentheses the reference will be undefinedundefinedpattern:$<name>.undefinedundefined

    undefinedundefined

    For example, let's reformat dates from "year-month-day" to "day.month.year":

    undefinedundefined

    run let regexp = /(?undefinedundefined [0-9]{4})-(?undefinedundefined [0-9]{2})-(?undefinedundefined [0-9]{2})/g;undefinedundefined

    undefinedundefined

    let str = "2019-10-30, 2020-01-01";

    undefinedundefined

    alert( str.replace(regexp, ‘undefinedundefined < undefinedundefineddundefinedundefinedaundefinedundefinedy > .undefinedundefinedundefinedundefined .$undefinedundefined''') ); // 30.10.2019, 01.01.2020 undefinedundefined

    undefinedundefined

    Non-capturing groups with ?:

    undefinedundefined

    Sometimes we need parentheses to correctly apply a quantifier, but we don't want their contents in results.

    undefinedundefined

    A group may be excluded by adding undefinedundefinedpattern:?: in the beginning.undefinedundefined

    undefinedundefined

    For instance, if we want to find undefinedundefinedpattern:(go)+, but don't want the parentheses contents (undefinedundefinedgo) as a separate array item, we can write: undefinedundefinedpattern:(?:go)+.undefinedundefined

    undefinedundefined

    In the example below we only get the name undefinedundefinedmatch:John as a separate member of the match:undefinedundefined

    undefinedundefined

    run let str = "Gogogo John!";

    undefinedundefined

    undefinedundefined! // ?: exludes ‘go' from capturing let regexp = /(?:go)+ (+)/i; undefinedundefined/!undefinedundefined

    undefinedundefined

    let result = str.match(regexp);

    undefinedundefined

    alert( result[0] ); // Gogogo John (full match) alert( result[1] ); // John alert( result.length ); // 2 (no more items in the array)

    undefinedundefined

    Summary

    undefinedundefined

    Parentheses group together a part of the regular expression, so that the quantifier applies to it as a whole.

    undefinedundefined

    Parentheses groups are numbered left-to-right, and can optionally be named with undefinedundefined(?<name>...).undefinedundefined

    undefinedundefined

    The content, matched by a group, can be obtained in the results:

    undefinedundefined
      undefinedundefined
    • The method undefinedundefinedstr.match returns capturing groups only without flag undefinedundefinedpattern:g.undefinedundefined
    • undefinedundefined
    • The method undefinedundefinedstr.matchAll always returns capturing groups.undefinedundefined
    • undefinedundefined
    undefinedundefined

    If the parentheses have no name, then their contents is available in the match array by its number. Named parentheses are also available in the property undefinedundefinedgroups.undefinedundefined

    undefinedundefined

    We can also use parentheses contents in the replacement string in undefinedundefinedstr.replace: by the number undefinedundefined$n or the name undefinedundefined$<name>.undefinedundefined

    undefinedundefined

    A group may be excluded from numbering by adding undefinedundefinedpattern:?: in its start. That's used when we need to apply a quantifier to the whole group, but don't want it as a separate item in the results array. We also can't reference such parentheses in the replacement string.undefinedundefined

    undefinedundefined

    Backreferences in pattern: and name>

    undefinedundefined

    We can use the contents of capturing groups undefinedundefinedpattern:(...) not only in the result or in the replacement string, but also in the pattern itself.undefinedundefined

    undefinedundefined

    Backreference by number:

    undefinedundefined

    A group can be referenced in the pattern using undefinedundefinedpattern:\N, where undefinedundefinedN is the group number.undefinedundefined

    undefinedundefined

    To make clear why that's helpful, let's consider a task.

    undefinedundefined

    We need to find quoted strings: either single-quoted undefinedundefinedsubject:'...' or a double-quoted undefinedundefinedsubject:"..." - both variants should match.undefinedundefined

    undefinedundefined

    How to find them?

    undefinedundefined

    We can put both kinds of quotes in the square brackets: undefinedundefinedpattern:['"](.*?)['"], but it would find strings with mixed quotes, like undefinedundefinedmatch:"...' and undefinedundefinedmatch:'...". That would lead to incorrect matches when one quote appears inside other ones, like in the string undefinedundefinedsubject:"She's the one!":undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let str =He said: "She's the one!".`;undefinedundefined

    undefinedundefined

    let regexp = /undefinedundefined'''"['''"]/g;undefinedundefined

    undefinedundefined

    // The result is not what we'd like to have alert( str.match(regexp) ); // "She'

    undefinedundefined

    As we can see, the pattern found an opening quote undefinedundefinedmatch:", then the text is consumed till the other quote undefinedundefinedmatch:', that closes the match.undefinedundefined

    undefinedundefined

    To make sure that the pattern looks for the closing quote exactly the same as the opening one, we can wrap it into a capturing group and backreference it: undefinedundefinedpattern:(['"])(.*?)\1.undefinedundefined

    undefinedundefined

    Here's the correct code:

    undefinedundefined

    ``undefinedundefinedjs run let str =He said: "She's the one!".`;undefinedundefined

    undefinedundefined

    undefinedundefined! let regexp = /(['''"])(.undefinedundefined?)\1/g; /!*undefinedundefined

    undefinedundefined

    alert( str.match(regexp) ); // "She's the one!"

    undefinedundefined

    Now it works! The regular expression engine finds the first quote undefinedundefinedpattern:(['"]) and memorizes its content. That's the first capturing group.undefinedundefined

    undefinedundefined

    Further in the pattern undefinedundefinedpattern:\1 means "find the same text as in the first group", exactly the same quote in our case.undefinedundefined

    undefinedundefined

    Similar to that, undefinedundefinedpattern:\2 would mean the contents of the second group, undefinedundefinedpattern:\3 - the 3rd group, and so on.undefinedundefined

    undefinedundefined
    undefinedundefinedIf we use `?:` in the group, then we can't reference it. Groups that are excluded from capturing `(?:...)` are not memorized by the engine.undefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="Don't mess up: in the pattern `pattern:\1`, in the replacement: `pattern:$1`" In the replacement string we use a dollar sign: `pattern:$1`, while in the pattern - a backslash `pattern:\1`.undefinedundefined

    undefinedundefined

    Backreference by name: undefinedundefined\k<name>undefinedundefined

    undefinedundefined

    If a regexp has many parentheses, it's convenient to give them names.

    undefinedundefined

    To reference a named group we can use undefinedundefinedpattern:\k<name>.undefinedundefined

    undefinedundefined

    In the example below the group with quotes is named undefinedundefinedpattern:?<quote>, so the backreference is undefinedundefinedpattern:\k<quote>:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let str =He said: "She's the one!".`;undefinedundefined

    undefinedundefined

    undefinedundefined! let regexp = /(?undefinedundefined ['''"])(.undefinedundefined?)quote>/g; /!*undefinedundefined

    undefinedundefined

    alert( str.match(regexp) ); // "She's the one!"

    undefinedundefined

    Alternation (OR) |

    undefinedundefined

    Alternation is the term in regular expression that is actually a simple "OR".

    undefinedundefined

    In a regular expression it is denoted with a vertical line character undefinedundefinedpattern:|.undefinedundefined

    undefinedundefined

    For instance, we need to find programming languages: HTML, PHP, Java or JavaScript.

    undefinedundefined

    The corresponding regexp: undefinedundefinedpattern:html|php|java(script)?.undefinedundefined

    undefinedundefined

    A usage example:

    undefinedundefined

    run let regexp = /html|php|css|java(script)?/gi;

    undefinedundefined

    let str = "First HTML appeared, then CSS, then JavaScript";

    undefinedundefined

    alert( str.match(regexp) ); // ‘HTML', ‘CSS', ‘JavaScript'

    undefinedundefined
    undefinedundefined
    We already saw a similar thing -- square brackets. They allow to choose between multiple characters, for instance `pattern:gr[ae]y` matches `match:gray` or `match:grey`.
    Square brackets allow only characters or character classes. Alternation allows any expressions. A regexp `pattern:A|B|C` means one of expressions `A`, `B` or `C`.
    For instance:
    - `pattern:gr(a|e)y` means exactly the same as `pattern:gr[ae]y`.
    - `pattern:gra|ey` means `match:gra` or `match:ey`.
    To apply alternation to a chosen part of the pattern, we can enclose it in parentheses:
    - `pattern:I love HTML|CSS` matches `match:I love HTML` or `match:CSS`.
    - `pattern:I love (HTML|CSS)` matches `match:I love HTML` or `match:I love CSS`.
    ## Example: regexp for time
    In previous articles there was a task to build a regexp for searching time in the form `hh:mm`, for instance `12:00`. But a simple `pattern:\d\d:\d\d` is too vague. It accepts `25:99` as the time (as 99 minutes match the pattern, but that time is invalid).
    How can we make a better pattern?
    We can use more careful matching. First, the hours:
    - If the first digit is `0` or `1`, then the next digit can be any: `pattern:[01]\d`.
    - Otherwise, if the first digit is `2`, then the next must be `pattern:[0-3]`.
    - (no other first digit is allowed)
    We can write both variants in a regexp using alternation: `pattern:[01]\d|2[0-3]`.
    Next, minutes must be from `00` to `59`. In the regular expression language that can be written as `pattern:[0-5]\d`: the first digit `0-5`, and then any digit.
    If we glue hours and minutes together, we get the pattern: `pattern:[01]\d|2[0-3]:[0-5]\d`.
    We're almost done, but there's a problem. The alternation `pattern:|` now happens to be between `pattern:[01]\d` and `pattern:2[0-3]:[0-5]\d`.
    That is: minutes are added to the second alternation variant, here's a clear picture:
    undefinedundefined
    undefinedundefined

    [01] 2[0-3]:[0-5]``

    undefinedundefined

    That pattern looks for undefinedundefinedpattern:[01]\d or undefinedundefinedpattern:2[0-3]:[0-5]\d.undefinedundefined

    undefinedundefined

    But that's wrong, the alternation should only be used in the "hours" part of the regular expression, to allow undefinedundefinedpattern:[01]\d OR undefinedundefinedpattern:2[0-3]. Let's correct that by enclosing "hours" into parentheses: undefinedundefinedpattern:([01]\d|2[0-3]):[0-5]\d.undefinedundefined

    undefinedundefined

    The final solution:

    undefinedundefined

    run let regexp = /([01]2[0-3]):[0-5]g;

    undefinedundefined

    alert("00:00 10:10 23:59 25:99 1:2".match(regexp)); // 00:00,10:10,23:59

    undefinedundefined

    Loops: while and for

    undefinedundefined

    We often need to repeat actions.

    undefinedundefined

    For example, outputting goods from a list one after another or just running the same code for each number from 1 to 10.

    undefinedundefined

    undefinedundefinedLoops are a way to repeat the same code multiple times.undefinedundefined

    undefinedundefined

    The "while" loop

    undefinedundefined

    The undefinedundefinedwhile loop has the following syntax:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedwhile (condition) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// codeundefinedundefinedundefinedundefinedundefinedundefined// so-called "loop body"undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    While the undefinedundefinedcondition is truthy, the undefinedundefinedcode from the loop body is executed.undefinedundefined

    undefinedundefined

    For instance, the loop below outputs undefinedundefinedi while undefinedundefinedi < 3:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let i = 0; while (i < 3) { // shows 0, then 1, then 2 alert( i ); i++; }undefinedundefined

    undefinedundefined

    A single execution of the loop body is called undefinedundefinedan iteration. The loop in the example above makes three iterations.undefinedundefined

    undefinedundefined

    If undefinedundefinedi++ was missing from the example above, the loop would repeat (in theory) forever. In practice, the browser provides ways to stop such loops, and in server-side JavaScript, we can kill the process.undefinedundefined

    undefinedundefined

    Any expression or variable can be a loop condition, not just comparisons: the condition is evaluated and converted to a boolean by undefinedundefinedwhile.undefinedundefined

    undefinedundefined

    For instance, a shorter way to write undefinedundefinedwhile (i != 0) is undefinedundefinedwhile (i):undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let i = 3; *!* while (i) { // when i becomes 0, the condition becomes falsy, and the loop stops */!* alert( i ); i--; }undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Curly braces are not required for a single-line body" If the loop body has a single statement, we can omit the curly braces{…}`:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let i = 3; *!* while (i) alert(i--); */!* undefinedundefined

    undefinedundefined

    The "do..while" loop

    undefinedundefined

    The condition check can be moved undefinedundefinedbelow the loop body using the undefinedundefineddo..while syntax:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineddoundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// loop bodyundefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedwhile (condition)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The loop will first execute the body, then check the condition, and, while it's truthy, execute it again and again.

    undefinedundefined

    For example:

    undefinedundefined

    undefinedundefinedjs run let i = 0; do { alert( i ); i++; } while (i < 3);undefinedundefined

    undefinedundefined

    This form of syntax should only be used when you want the body of the loop to execute undefinedundefinedat least once regardless of the condition being truthy. Usually, the other form is preferred: undefinedundefinedwhile(…) {…}.undefinedundefined

    undefinedundefined

    The "for" loop

    undefinedundefined

    The undefinedundefinedfor loop is more complex, but it's also the most commonly used loop.undefinedundefined

    undefinedundefined

    It looks like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfor (beginundefinedundefined; conditionundefinedundefined; step) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ... loop body ...undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Let's learn the meaning of these parts by example. The loop below runs undefinedundefinedalert(i) for undefinedundefinedi from undefinedundefined0 up to (but not including) undefinedundefined3:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2 alert(i); }undefinedundefined

    undefinedundefined

    Let's examine the undefinedundefinedfor statement part-by-part:undefinedundefined

    undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined
    part
    begin undefinedundefinedi = 0undefinedundefinedExecutes once upon entering the loop.
    condition undefinedundefinedi < 3undefinedundefined Checked before every loop iteration. If false, the loop stops.
    body undefinedundefinedalert(i)undefinedundefined Runs again and again while the condition is truthy.
    step undefinedundefinedi++undefinedundefinedExecutes after the body on each iteration.
    undefinedundefined

    The general loop algorithm works like this:

    undefinedundefined
    undefinedundefinedRun begin
    → (if condition → run body and run step)
    → (if condition → run body and run step)
    → (if condition → run body and run step)
    → ...undefinedundefined
    undefinedundefined

    That is, undefinedundefinedbegin executes once, and then it iterates: after each undefinedundefinedcondition test, undefinedundefinedbody and undefinedundefinedstep are executed.undefinedundefined

    undefinedundefined

    If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper.

    undefinedundefined

    Here's exactly what happens in our case:

    undefinedundefinedundefinedundefined

    undefinedundefinedsmart header="Inline variable declaration" Here, the "counter" variablei` is declared right in the loop. This is called an "inline" variable declaration. Such variables are visible only inside the loop.undefinedundefined

    undefinedundefined

    undefinedundefinedjs run for (*!*let*/!* i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } alert(i); // error, no such variableundefinedundefined

    undefinedundefined

    Instead of defining a variable, we could use an existing one:

    undefinedundefined

    run let i = 0;

    undefinedundefined

    for (i = 0; i < 3; i++) { // use an existing variable alert(i); // 0, 1, 2 }

    undefinedundefined

    alert(i); // 3, visible, because declared outside of the loop

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    Skipping parts

    undefinedundefined

    Any part of undefinedundefinedfor can be skipped.undefinedundefined

    undefinedundefined

    For example, we can omit undefinedundefinedbegin if we don't need to do anything at the loop start.undefinedundefined

    undefinedundefined

    Like here:

    undefinedundefined

    run let i = 0; // we have i already declared and assigned

    undefinedundefined

    for (; i < 3; i++) { // no need for "begin" alert( i ); // 0, 1, 2 }

    undefinedundefined

    We can also remove the undefinedundefinedstep part:undefinedundefined

    undefinedundefined

    run let i = 0;

    undefinedundefined

    for (; i < 3;) { alert( i++ ); }

    undefinedundefined

    This makes the loop identical to undefinedundefinedwhile (i < 3).undefinedundefined

    undefinedundefined

    We can actually remove everything, creating an infinite loop:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfor (undefinedundefined;;) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// repeats without limitsundefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Please note that the two undefinedundefinedfor semicolons undefinedundefined; must be present. Otherwise, there would be a syntax error.undefinedundefined

    undefinedundefined

    Breaking the loop

    undefinedundefined

    Normally, a loop exits when its condition becomes falsy.

    undefinedundefined

    But we can force the exit at any time using the special undefinedundefinedbreak directive.undefinedundefined

    undefinedundefined

    For example, the loop below asks the user for a series of numbers, "breaking" when no number is entered:

    undefinedundefined

    run let sum = 0;

    undefinedundefined

    while (true) {

    undefinedundefined

    let value = +prompt("Enter a number", '');

    undefinedundefined

    undefinedundefined! if (!value) break; // (undefinedundefined) /!*undefinedundefined

    undefinedundefined

    sum += value;

    undefinedundefined

    } alert( ‘Sum:''' + sum );

    undefinedundefined

    The undefinedundefinedbreak directive is activated at the line undefinedundefined(*) if the user enters an empty line or cancels the input. It stops the loop immediately, passing control to the first line after the loop. Namely, undefinedundefinedalert.undefinedundefined

    undefinedundefined

    The combination "infinite loop + undefinedundefinedbreak as needed" is great for situations when a loop's condition must be checked not in the beginning or end of the loop, but in the middle or even in several places of its body.undefinedundefined

    undefinedundefined

    Continue to the next iteration [#continue]

    undefinedundefined

    The undefinedundefinedcontinue directive is a "lighter version" of undefinedundefinedbreak. It doesn't stop the whole loop. Instead, it stops the current iteration and forces the loop to start a new one (if the condition allows).undefinedundefined

    undefinedundefined

    We can use it if we're done with the current iteration and would like to move on to the next one.

    undefinedundefined

    The loop below uses undefinedundefinedcontinue to output only odd values:undefinedundefined

    undefinedundefined

    run no-beautify for (let i = 0; i < 10; i++) {

    undefinedundefined

    // if true, skip the remaining part of the body undefinedundefined!if (i % 2 == 0) continue;undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(i); // 1, then 3, 5, 7, 9 }

    undefinedundefined

    For even values of undefinedundefinedi, the undefinedundefinedcontinue directive stops executing the body and passes control to the next iteration of undefinedundefinedfor (with the next number). So the undefinedundefinedalert is only called for odd values.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Thecontinue` directive helps decrease nesting" A loop that shows odd values could look like this:undefinedundefined

    undefinedundefined

    run for (let i = 0; i < 10; i++) {

    undefinedundefined

    if (i % 2) { alert( i ); }

    undefinedundefined

    }

    undefinedundefined
    undefinedundefined
    From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`.
    But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.undefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="Nobreak/continueundefinedundefinedto the right side of '?'" Please note that syntax constructs that are not expressions cannot be used with the ternary operator?undefinedundefined. In particular, directives such asbreak/continue` aren't allowed there.undefinedundefined

    undefinedundefined

    For example, if we take this code:

    undefinedundefinedundefinedundefined

    …and rewrite it using a question mark:

    undefinedundefined

    undefinedundefinedjs no-beautify (i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed hereundefinedundefined

    undefinedundefined

    …it stops working: there's a syntax error.

    undefinedundefined

    This is just another reason not to use the question mark operator undefinedundefined? instead of undefinedundefinedif. undefinedundefined

    undefinedundefined

    Labels for break/continue

    undefinedundefined

    Sometimes we need to break out from multiple nested loops at once.

    undefinedundefined

    For example, in the code below we loop over undefinedundefinedi and undefinedundefinedj, prompting for the coordinates undefinedundefined(i, j) from undefinedundefined(0,0) to undefinedundefined(2,2):undefinedundefined

    undefinedundefined

    run no-beautify for (let i = 0; i < 3; i++) {

    undefinedundefined

    for (let j = 0; j < 3; j++) {

    undefinedundefined
    undefinedundefinedlet input = prompt(`Value at coords (${i},${j})`, '');
    // what if we want to exit from here to Done (below)?undefinedundefined
    undefinedundefined

    } }

    undefinedundefined

    alert(‘Done!''');

    undefinedundefined

    We need a way to stop the process if the user cancels the input.

    undefinedundefined

    The ordinary undefinedundefinedbreak after undefinedundefinedinput would only break the inner loop. That's not sufficient - labels, come to the rescue!undefinedundefined

    undefinedundefined

    A undefinedundefinedlabel is an identifier with a colon before a loop:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedlabelNameundefinedundefined:undefinedundefinedfor (...) undefinedundefined{undefinedundefinedundefinedundefined  ...undefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The undefinedundefinedbreak <labelName> statement in the loop below breaks out to the label:undefinedundefined

    undefinedundefined

    run no-beautify undefinedundefined!outer:undefinedundefined/! for (let i = 0; i < 3; i++) {undefinedundefined

    undefinedundefined

    for (let j = 0; j < 3; j++) {

    undefinedundefined
    undefinedundefinedlet input = prompt(`Value at coords (${i},${j})`, '');
    // if an empty string or canceled, then break out of both loops
    if (!input) *!*break outer*/!*; // (*)
    // do something with the value...undefinedundefined
    undefinedundefined

    } } alert(‘Done!''');

    undefinedundefined

    In the code above, undefinedundefinedbreak outer looks upwards for the label named undefinedundefinedouter and breaks out of that loop.undefinedundefined

    undefinedundefined

    So the control goes straight from undefinedundefined(*) to undefinedundefinedalert('Done!').undefinedundefined

    undefinedundefined

    We can also move the label onto a separate line:

    undefinedundefined

    undefinedundefinedjs no-beautify outer: for (let i = 0; i < 3; i++) { ... }undefinedundefined

    undefinedundefined

    The undefinedundefinedcontinue directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop.undefinedundefined

    undefinedundefined

    warn header="Labels do not allow to "jump" anywhere" Labels do not allow us to jump into an arbitrary place in the code.

    undefinedundefined

    For example, it is impossible to do this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedbreak labelundefinedundefined;undefinedundefined// jump to the label below (doesn't work)undefinedundefinedundefinedundefinedundefinedundefinedlabelundefinedundefined:undefinedundefinedfor (...)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    A undefinedundefinedbreak directive must be inside a code block. Technically, any labelled code block will do, e.g.:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedlabelundefinedundefined:undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefinedbreak labelundefinedundefined;undefinedundefined// worksundefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    …Although, 99.9% of the time undefinedundefinedbreak used is inside loops, as we've seen in the examples above.undefinedundefined

    undefinedundefined

    A undefinedundefinedcontinue is only possible from inside a loop. undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    We covered 3 types of loops:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedwhile - The condition is checked before each iteration.undefinedundefined
    • undefinedundefined
    • undefinedundefineddo..while - The condition is checked after each iteration.undefinedundefined
    • undefinedundefined
    • undefinedundefinedfor (;;) - The condition is checked before each iteration, additional settings available.undefinedundefined
    • undefinedundefined
    undefinedundefined

    To make an "infinite" loop, usually the undefinedundefinedwhile(true) construct is used. Such a loop, just like any other, can be stopped with the undefinedundefinedbreak directive.undefinedundefined

    undefinedundefined

    If we don't want to do anything in the current iteration and would like to forward to the next one, we can use the undefinedundefinedcontinue directive.undefinedundefined

    undefinedundefined

    undefinedundefinedbreak/continue support labels before the loop. A label is the only way for undefinedundefinedbreak/continue to escape a nested loop to go to an outer one.undefinedundefined

    undefinedundefined

    Lookahead and lookbehind

    undefinedundefined

    Sometimes we need to find only those matches for a pattern that are followed or preceded by another pattern.

    undefinedundefined

    There's a special syntax for that, called "lookahead" and "lookbehind", together referred to as "lookaround".

    undefinedundefined

    For the start, let's find the price from the string like undefinedundefinedsubject:1 turkey costs 30€. That is: a number, followed by undefinedundefinedsubject:€ sign.undefinedundefined

    undefinedundefined

    Lookahead

    undefinedundefined

    The syntax is: undefinedundefinedpattern:X(?=Y), it means "look for undefinedundefinedpattern:X, but match only if followed by undefinedundefinedpattern:Y". There may be any pattern instead of undefinedundefinedpattern:X and undefinedundefinedpattern:Y.undefinedundefined

    undefinedundefined

    For an integer number followed by undefinedundefinedsubject:€, the regexp will be undefinedundefinedpattern:\d+(?=€):undefinedundefined

    undefinedundefined

    run let str = "1 turkey costs 30€";

    undefinedundefined

    alert( str.match(/(?=€)/) ); // 30, the number 1 is ignored, as it's not followed by €

    undefinedundefined

    Please note: the lookahead is merely a test, the contents of the parentheses undefinedundefinedpattern:(?=...) is not included in the result undefinedundefinedmatch:30.undefinedundefined

    undefinedundefined

    When we look for undefinedundefinedpattern:X(?=Y), the regular expression engine finds undefinedundefinedpattern:X and then checks if there's undefinedundefinedpattern:Y immediately after it. If it's not so, then the potential match is skipped, and the search continues.undefinedundefined

    undefinedundefined

    More complex tests are possible, e.g. undefinedundefinedpattern:X(?=Y)(?=Z) means:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Find undefinedundefinedpattern:X.undefinedundefined
    2. undefinedundefined
    3. Check if undefinedundefinedpattern:Y is immediately after undefinedundefinedpattern:X (skip if isn't).undefinedundefined
    4. undefinedundefined
    5. Check if undefinedundefinedpattern:Z is also immediately after undefinedundefinedpattern:X (skip if isn't).undefinedundefined
    6. undefinedundefined
    7. If both tests passed, then the undefinedundefinedpattern:X is a match, otherwise continue searching.undefinedundefined
    8. undefinedundefined
    undefinedundefined

    In other words, such pattern means that we're looking for undefinedundefinedpattern:X followed by undefinedundefinedpattern:Y and undefinedundefinedpattern:Z at the same time.undefinedundefined

    undefinedundefined

    That's only possible if patterns undefinedundefinedpattern:Y and undefinedundefinedpattern:Z aren't mutually exclusive.undefinedundefined

    undefinedundefined

    For example, undefinedundefinedpattern:\d+(?=\s)(?=.*30) looks for undefinedundefinedpattern:\d+ that is followed by a space undefinedundefinedpattern:(?=\s), and there's undefinedundefined30 somewhere after it undefinedundefinedpattern:(?=.*30):undefinedundefined

    undefinedundefined

    run let str = "1 turkey costs 30€";

    undefinedundefined

    alert( str.match(/(?=)(?=.*30)/) ); // 1

    undefinedundefined

    In our string that exactly matches the number undefinedundefined1.undefinedundefined

    undefinedundefined

    Negative lookahead

    undefinedundefined

    Let's say that we want a quantity instead, not a price from the same string. That's a number undefinedundefinedpattern:\d+, NOT followed by undefinedundefinedsubject:€.undefinedundefined

    undefinedundefined

    For that, a negative lookahead can be applied.

    undefinedundefined

    The syntax is: undefinedundefinedpattern:X(?!Y), it means "search undefinedundefinedpattern:X, but only if not followed by undefinedundefinedpattern:Y".undefinedundefined

    undefinedundefined

    run let str = "2 turkeys cost 60€";

    undefinedundefined

    alert( str.match(/?!€)/g) ); // 2 (the price is not matched)

    undefinedundefined

    Lookbehind

    undefinedundefined

    Lookahead allows to add a condition for "what follows".

    undefinedundefined

    Lookbehind is similar, but it looks behind. That is, it allows to match a pattern only if there's something before it.

    undefinedundefined

    The syntax is: - Positive lookbehind: undefinedundefinedpattern:(?<=Y)X, matches undefinedundefinedpattern:X, but only if there's undefinedundefinedpattern:Y before it. - Negative lookbehind: undefinedundefinedpattern:(?<!Y)X, matches undefinedundefinedpattern:X, but only if there's no undefinedundefinedpattern:Y before it.undefinedundefined

    undefinedundefined

    For example, let's change the price to US dollars. The dollar sign is usually before the number, so to look for undefinedundefined$30 we'll use undefinedundefinedpattern:(?<=\$)\d+ - an amount preceded by undefinedundefinedsubject:$:undefinedundefined

    undefinedundefined

    run let str = "1 turkey costs $30";

    undefinedundefined

    // the dollar sign is escaped $ alert( str.match(/(?<=$)/) ); // 30 (skipped the sole number)

    undefinedundefined

    And, if we need the quantity - a number, not preceded by undefinedundefinedsubject:$, then we can use a negative lookbehind undefinedundefinedpattern:(?<!\$)\d+:undefinedundefined

    undefinedundefined

    run let str = "2 turkeys cost $60";

    undefinedundefined

    alert( str.match(/(?<!$)/g) ); // 2 (the price is not matched)

    undefinedundefined

    Capturing groups

    undefinedundefined

    Generally, the contents inside lookaround parentheses does not become a part of the result.

    undefinedundefined

    E.g. in the pattern undefinedundefinedpattern:\d+(?=€), the undefinedundefinedpattern:€ sign doesn't get captured as a part of the match. That's natural: we look for a number undefinedundefinedpattern:\d+, while undefinedundefinedpattern:(?=€) is just a test that it should be followed by undefinedundefinedsubject:€.undefinedundefined

    undefinedundefined

    But in some situations we might want to capture the lookaround expression as well, or a part of it. That's possible. Just wrap that part into additional parentheses.

    undefinedundefined

    In the example below the currency sign undefinedundefinedpattern:(€|kr) is captured, along with the amount:undefinedundefined

    undefinedundefined

    run let str = "1 turkey costs 30€"; let regexp = /(?=(€|kr))/; // extra parentheses around €|kr

    undefinedundefined

    alert( str.match(regexp) ); // 30, €

    undefinedundefined

    And here's the same for lookbehind:

    undefinedundefined

    run let str = "1 turkey costs $30"; let regexp = /(?<=($|£))/;

    undefinedundefined

    alert( str.match(regexp) ); // 30, $

    undefinedundefined

    Summary

    undefinedundefined

    Lookahead and lookbehind (commonly referred to as "lookaround") are useful when we'd like to match something depending on the context before/after it.

    undefinedundefined

    For simple regexps we can do the similar thing manually. That is: match everything, in any context, and then filter by context in the loop.

    undefinedundefined

    Remember, undefinedundefinedstr.match (without flag undefinedundefinedpattern:g) and undefinedundefinedstr.matchAll (always) return matches as arrays with undefinedundefinedindex property, so we know where exactly in the text it is, and can check the context.undefinedundefined

    undefinedundefined

    But generally lookaround is more convenient.

    undefinedundefined

    Lookaround types:

    undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined
    Patterntypematches
    undefinedundefinedX(?=Y)undefinedundefinedPositive lookaheadundefinedundefinedpattern:X if followed by undefinedundefinedpattern:Yundefinedundefined
    undefinedundefinedX(?!Y)undefinedundefinedNegative lookaheadundefinedundefinedpattern:X if not followed by undefinedundefinedpattern:Yundefinedundefined
    undefinedundefined(?<=Y)Xundefinedundefined Positive lookbehindundefinedundefinedpattern:X if after undefinedundefinedpattern:Yundefinedundefined
    undefinedundefined(?<!Y)Xundefinedundefined Negative lookbehindundefinedundefinedpattern:X if not after undefinedundefinedpattern:Yundefinedundefined
    undefinedundefined

    Catastrophic backtracking

    undefinedundefined

    Some regular expressions are looking simple, but can execute a veeeeeery long time, and even "hang" the JavaScript engine.

    undefinedundefined

    Sooner or later most developers occasionally face such behavior. The typical symptom - a regular expression works fine sometimes, but for certain strings it "hangs", consuming 100% of CPU.

    undefinedundefined

    In such case a web-browser suggests to kill the script and reload the page. Not a good thing for sure.

    undefinedundefined

    For server-side JavaScript such a regexp may hang the server process, that's even worse. So we definitely should take a look at it.

    undefinedundefined

    Example

    undefinedundefined

    Let's say we have a string, and we'd like to check if it consists of words undefinedundefinedpattern:\w+ with an optional space undefinedundefinedpattern:\s? after each.undefinedundefined

    undefinedundefined

    An obvious way to construct a regexp would be to take a word followed by an optional space undefinedundefinedpattern:\w+\s? and then repeat it with undefinedundefined*.undefinedundefined

    undefinedundefined

    That leads us to the regexp undefinedundefinedpattern:^(\w+\s?)*$, it specifies zero or more such words, that start at the beginning undefinedundefinedpattern:^ and finish at the end undefinedundefinedpattern:$ of the line.undefinedundefined

    undefinedundefined

    In action:

    undefinedundefined

    run let regexp = /^(+?)*$/;

    undefinedundefined

    alert( regexp.test("A good string") ); // true alert( regexp.test("Bad characters: $@#") ); // false

    undefinedundefined

    The regexp seems to work. The result is correct. Although, on certain strings it takes a lot of time. So long that JavaScript engine "hangs" with 100% CPU consumption.

    undefinedundefined

    If you run the example below, you probably won't see anything, as JavaScript will just "hang". A web-browser will stop reacting on events, the UI will stop working (most browsers allow only scrolling). After some time it will suggest to reload the page. So be careful with this:

    undefinedundefined

    run let regexp = /^(+?)*$/; let str = "An input string that takes a long time or even makes this regexp hang!";

    undefinedundefined

    // will take a very long time alert( regexp.test(str) );

    undefinedundefined

    To be fair, let's note that some regular expression engines can handle such a search effectively, for example V8 engine version starting from 8.8 can do that (so Google Chrome 88 doesn't hang here), while Firefox browser does hang.

    undefinedundefined

    Simplified example

    undefinedundefined

    What's the matter? Why does the regular expression hang?

    undefinedundefined

    To understand that, let's simplify the example: remove spaces undefinedundefinedpattern:\s?. Then it becomes undefinedundefinedpattern:^(\w+)*$.undefinedundefined

    undefinedundefined

    And, to make things more obvious, let's replace undefinedundefinedpattern:\w with undefinedundefinedpattern:\d. The resulting regular expression still hangs, for instance:undefinedundefined

    undefinedundefined

    run let regexp = /^()*$/;

    undefinedundefined

    let str = "012345678901234567890123456789z";

    undefinedundefined

    // will take a very long time (careful!) alert( regexp.test(str) );

    undefinedundefined
    undefinedundefined
    So what's wrong with the regexp?
    First, one may notice that the regexp `pattern:(\d+)*` is a little bit strange. The quantifier `pattern:*` looks extraneous. If we want a number, we can use `pattern:\d+`.
    Indeed, the regexp is artificial; we got it by simplifying the previous example. But the reason why it is slow is the same. So let's understand it, and then the previous example will become obvious.
    What happens during the search of `pattern:^(\d+)*$` in the line `subject:123456789z` (shortened a bit for clarity, please note a non-digit character `subject:z` at the end, it's important), why does it take so long?
    Here's what the regexp engine does:
    1. First, the regexp engine tries to find the content of the parentheses: the number `pattern:\d+`. The plus `pattern:+` is greedy by default, so it consumes all digits:
    \d+.......
    (123456789)z
    After all digits are consumed, `pattern:\d+` is considered found (as `match:123456789`).
    Then the star quantifier `pattern:(\d+)*` applies. But there are no more digits in the text, so the star doesn't give anything.
    The next character in the pattern is the string end `pattern:$`. But in the text we have `subject:z` instead, so there's no match:
    X
    \d+........$
    (123456789)z
    2. As there's no match, the greedy quantifier `pattern:+` decreases the count of repetitions, backtracks one character back.
    Now `pattern:\d+` takes all digits except the last one (`match:12345678`):
    \d+.......
    (12345678)9z
    3. Then the engine tries to continue the search from the next position (right after `match:12345678`).
    The star `pattern:(\d+)*` can be applied -- it gives one more match of `pattern:\d+`, the number `match:9`:
    \d+.......\d+
    (12345678)(9)z
    The engine tries to match `pattern:$` again, but fails, because it meets `subject:z` instead:
    X
    \d+.......\d+
    (12345678)(9)z
    4. There's no match, so the engine will continue backtracking, decreasing the number of repetitions. Backtracking generally works like this: the last greedy quantifier decreases the number of repetitions until it reaches the minimum. Then the previous greedy quantifier decreases, and so on.
    All possible combinations are attempted. Here are their examples.
    The first number `pattern:\d+` has 7 digits, and then a number of 2 digits:
    X
    \d+......\d+
    (1234567)(89)z
    The first number has 7 digits, and then two numbers of 1 digit each:
    X
    \d+......\d+\d+
    (1234567)(8)(9)z
    The first number has 6 digits, and then a number of 3 digits:
    X
    \d+.......\d+
    (123456)(789)z
    The first number has 6 digits, and then 2 numbers:
    X
    \d+.....\d+ \d+
    (123456)(78)(9)z
    ...And so on.
    There are many ways to split a sequence of digits `123456789` into numbers. To be precise, there are <code>2<sup>n</sup>-1</code>, where `n` is the length of the sequence.
    - For `123456789` we have `n=9`, that gives 511 combinations.
    - For a longer sequence with `n=20` there are about one million (1048575) combinations.
    - For `n=30` - a thousand times more (1073741823 combinations).
    Trying each of them is exactly the reason why the search takes so long.
    ## Back to words and strings
    The similar thing happens in our first example, when we look for words by pattern `pattern:^(\w+\s?)*$` in the string `subject:An input that hangs!`.
    The reason is that a word can be represented as one `pattern:\w+` or many:
    undefinedundefined
    undefinedundefined

    (input) (inpu)(t) (inp)(u)(t) (in)(p)(ut) …

    undefinedundefined

    For a human, it's obvious that there may be no match, because the string ends with an exclamation sign undefinedundefined!, but the regular expression expects a wordly character undefinedundefinedpattern:\w or a space undefinedundefinedpattern:\s at the end. But the engine doesn't know that.undefinedundefined

    undefinedundefined

    It tries all combinations of how the regexp undefinedundefinedpattern:(\w+\s?)* can "consume" the string, including variants with spaces undefinedundefinedpattern:(\w+\s)* and without them undefinedundefinedpattern:(\w+)* (because spaces undefinedundefinedpattern:\s? are optional). As there are many such combinations (we've seen it with digits), the search takes a lot of time.undefinedundefined

    undefinedundefined

    What to do?

    undefinedundefined

    Should we turn on the lazy mode?

    undefinedundefined

    Unfortunately, that won't help: if we replace undefinedundefinedpattern:\w+ with undefinedundefinedpattern:\w+?, the regexp will still hang. The order of combinations will change, but not their total count.undefinedundefined

    undefinedundefined

    Some regular expression engines have tricky tests and finite automations that allow to avoid going through all combinations or make it much faster, but most engines don't, and it doesn't always help.

    undefinedundefined

    How to fix?

    undefinedundefined

    There are two main approaches to fixing the problem.

    undefinedundefined

    The first is to lower the number of possible combinations.

    undefinedundefined

    Let's make the space non-optional by rewriting the regular expression as undefinedundefinedpattern:^(\w+\s)*\w*$ - we'll look for any number of words followed by a space undefinedundefinedpattern:(\w+\s)*, and then (optionally) a final word undefinedundefinedpattern:\w*.undefinedundefined

    undefinedundefined

    This regexp is equivalent to the previous one (matches the same) and works well:

    undefinedundefined

    run let regexp = /^(+)*$/; let str = "An input string that takes a long time or even makes this regex hang!";

    undefinedundefined

    alert( regexp.test(str) ); // false

    undefinedundefined
    undefinedundefined
    Why did the problem disappear?
    That's because now the space is mandatory.
    The previous regexp, if we omit the space, becomes `pattern:(\w+)*`, leading to many combinations of `\w+` within a single word
    So `subject:input` could be matched as two repetitions of `pattern:\w+`, like this:
    undefinedundefined
    undefinedundefined

    + + (inp)(ut)

    undefinedundefined
    undefinedundefined
    The new pattern is different: `pattern:(\w+\s)*` specifies repetitions of words followed by a space! The `subject:input` string can't be matched as two repetitions of `pattern:\w+\s`, because the space is mandatory.
    The time needed to try a lot of (actually most of) combinations is now saved.
    ## Preventing backtracking
    It's not always convenient to rewrite a regexp though. In the example above it was easy, but it's not always obvious how to do it.
    Besides, a rewritten regexp is usually more complex, and that's not good. Regexps are complex enough without extra efforts.
    Luckily, there's an alternative approach. We can forbid backtracking for the quantifier.
    The root of the problem is that the regexp engine tries many combinations that are obviously wrong for a human.
    E.g. in the regexp `pattern:(\d+)*$` it's obvious for a human, that `pattern:+` shouldn't backtrack. If we replace one `pattern:\d+` with two separate `pattern:\d+\d+`, nothing changes:
    undefinedundefined
    undefinedundefined

    …….. (123456789)!

    undefinedundefined

    ……. (1234)(56789)!

    undefinedundefined

    And in the original example undefinedundefinedpattern:^(\w+\s?)*$ we may want to forbid backtracking in undefinedundefinedpattern:\w+. That is: undefinedundefinedpattern:\w+ should match a whole word, with the maximal possible length. There's no need to lower the repetitions count in undefinedundefinedpattern:\w+ or to split it into two words undefinedundefinedpattern:\w+\w+ and so on.undefinedundefined

    undefinedundefined

    Modern regular expression engines support possessive quantifiers for that. Regular quantifiers become possessive if we add undefinedundefinedpattern:+ after them. That is, we use undefinedundefinedpattern:\d++ instead of undefinedundefinedpattern:\d+ to stop undefinedundefinedpattern:+ from backtracking.undefinedundefined

    undefinedundefined

    Possessive quantifiers are in fact simpler than "regular" ones. They just match as many as they can, without any backtracking. The search process without bracktracking is simpler.

    undefinedundefined

    There are also so-called "atomic capturing groups" - a way to disable backtracking inside parentheses.

    undefinedundefined

    …But the bad news is that, unfortunately, in JavaScript they are not supported.

    undefinedundefined

    We can emulate them though using a "lookahead transform".

    undefinedundefined

    Lookahead to the rescue!

    undefinedundefined

    So we've come to real advanced topics. We'd like a quantifier, such as undefinedundefinedpattern:+ not to backtrack, because sometimes backtracking makes no sense.undefinedundefined

    undefinedundefined

    The pattern to take as many repetitions of undefinedundefinedpattern:\w as possible without backtracking is: undefinedundefinedpattern:(?=(\w+))\1. Of course, we could take another pattern instead of undefinedundefinedpattern:\w.undefinedundefined

    undefinedundefined

    That may seem odd, but it's actually a very simple transform.

    undefinedundefined

    Let's decipher it:

    undefinedundefined
      undefinedundefined
    • Lookahead undefinedundefinedpattern:?= looks forward for the longest word undefinedundefinedpattern:\w+ starting at the current position.undefinedundefined
    • undefinedundefined
    • The contents of parentheses with undefinedundefinedpattern:?=... isn't memorized by the engine, so wrap undefinedundefinedpattern:\w+ into parentheses. Then the engine will memorize their contentsundefinedundefined
    • undefinedundefined
    • …And allow us to reference it in the pattern as undefinedundefinedpattern:\1.undefinedundefined
    • undefinedundefined
    undefinedundefined

    That is: we look ahead - and if there's a word undefinedundefinedpattern:\w+, then match it as undefinedundefinedpattern:\1.undefinedundefined

    undefinedundefined

    Why? That's because the lookahead finds a word undefinedundefinedpattern:\w+ as a whole and we capture it into the pattern with undefinedundefinedpattern:\1. So we essentially implemented a possessive plus undefinedundefinedpattern:+ quantifier. It captures only the whole word undefinedundefinedpattern:\w+, not a part of it.undefinedundefined

    undefinedundefined

    For instance, in the word undefinedundefinedsubject:JavaScript it may not only match undefinedundefinedmatch:Java, but leave out undefinedundefinedmatch:Script to match the rest of the pattern.undefinedundefined

    undefinedundefined

    Here's the comparison of two patterns:

    undefinedundefined

    undefinedundefinedjs run alert( "JavaScript".match(/\w+Script/)); // JavaScript alert( "JavaScript".match(/(?=(\w+))\1Script/)); // nullundefinedundefined

    undefinedundefined
      undefinedundefined
    1. In the first variant undefinedundefinedpattern:\w+ first captures the whole word undefinedundefinedsubject:JavaScript but then undefinedundefinedpattern:+ backtracks character by character, to try to match the rest of the pattern, until it finally succeeds (when undefinedundefinedpattern:\w+ matches undefinedundefinedmatch:Java).undefinedundefined
    2. undefinedundefined
    3. In the second variant undefinedundefinedpattern:(?=(\w+)) looks ahead and finds the word undefinedundefinedsubject:JavaScript, that is included into the pattern as a whole by undefinedundefinedpattern:\1, so there remains no way to find undefinedundefinedsubject:Script after it.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    We can put a more complex regular expression into undefinedundefinedpattern:(?=(\w+))\1 instead of undefinedundefinedpattern:\w, when we need to forbid backtracking for undefinedundefinedpattern:+ after it.undefinedundefined

    undefinedundefined
    undefinedundefinedThere's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups).undefinedundefined
    undefinedundefined

    Let's rewrite the first example using lookahead to prevent backtracking:

    undefinedundefined

    run let regexp = /^((?=(+))\2?)*$/;

    undefinedundefined

    alert( regexp.test("A good string") ); // true

    undefinedundefined

    let str = "An input string that takes a long time or even makes this regex hang!";

    undefinedundefined

    alert( regexp.test(str) ); // false, works and fast!

    undefinedundefined

    Here undefinedundefinedpattern:\2 is used instead of undefinedundefinedpattern:\1, because there are additional outer parentheses. To avoid messing up with the numbers, we can give the parentheses a name, e.g. undefinedundefinedpattern:(?<word>\w+).undefinedundefined

    undefinedundefined

    run // parentheses are named ?undefinedundefined, referenced as word> let regexp = /^((?=(?undefinedundefined +))word>?)*$/;undefinedundefined

    undefinedundefined

    let str = "An input string that takes a long time or even makes this regex hang!";

    undefinedundefined

    alert( regexp.test(str) ); // false

    undefinedundefined

    alert( regexp.test("A correct string") ); // true

    undefinedundefined

    The problem described in this article is called "catastrophic backtracking".

    undefinedundefined

    We covered two ways how to solve it: - Rewrite the regexp to lower the possible combinations count. - Prevent backtracking.

    undefinedundefined

    Sticky flag "y", searching at position

    undefinedundefined

    The flag undefinedundefinedpattern:y allows to perform the search at the given position in the source string.undefinedundefined

    undefinedundefined

    To grasp the use case of undefinedundefinedpattern:y flag, and better understand the ways of regexps, let's explore a practical example.undefinedundefined

    undefinedundefined

    One of common tasks for regexps is "lexical analysis": we get a text, e.g. in a programming language, and need to find its structural elements. For instance, HTML has tags and attributes, JavaScript code has functions, variables, and so on.

    undefinedundefined

    Writing lexical analyzers is a special area, with its own tools and algorithms, so we don't go deep in there, but there's a common task: to read something at the given position.

    undefinedundefined

    E.g. we have a code string undefinedundefinedsubject:let varName = "value", and we need to read the variable name from it, that starts at position undefinedundefined4.undefinedundefined

    undefinedundefined

    We'll look for variable name using regexp undefinedundefinedpattern:\w+. Actually, JavaScript variable names need a bit more complex regexp for accurate matching, but here it doesn't matter.undefinedundefined

    undefinedundefined
      undefinedundefined
    • A call to undefinedundefinedstr.match(/\w+/) will find only the first word in the line (undefinedundefinedlet). That's not it.undefinedundefined
    • undefinedundefined
    • We can add the flag undefinedundefinedpattern:g. But then the call undefinedundefinedstr.match(/\w+/g) will look for all words in the text, while we need one word at position undefinedundefined4. Again, not what we need.undefinedundefined
    • undefinedundefined
    undefinedundefined

    undefinedundefinedSo, how to search for a regexp exactly at the given position?undefinedundefined

    undefinedundefined

    Let's try using method undefinedundefinedregexp.exec(str).undefinedundefined

    undefinedundefined

    For a undefinedundefinedregexp without flags undefinedundefinedpattern:g and undefinedundefinedpattern:y, this method looks only for the first match, it works exactly like undefinedundefinedstr.match(regexp).undefinedundefined

    undefinedundefined

    …But if there's flag undefinedundefinedpattern:g, then it performs the search in undefinedundefinedstr, starting from position stored in the undefinedundefinedregexp.lastIndex property. And, if it finds a match, then sets undefinedundefinedregexp.lastIndex to the index immediately after the match.undefinedundefined

    undefinedundefined

    In other words, undefinedundefinedregexp.lastIndex serves as a starting point for the search, that each undefinedundefinedregexp.exec(str) call resets to the new value ("after the last match"). That's only if there's undefinedundefinedpattern:g flag, of course.undefinedundefined

    undefinedundefined

    So, successive calls to undefinedundefinedregexp.exec(str) return matches one after another.undefinedundefined

    undefinedundefined

    Here's an example of such calls:

    undefinedundefined

    run let str = ‘let varName'; // Let's find all words in this string let regexp = /+/g;

    undefinedundefined

    alert(regexp.lastIndex); // 0 (initially lastIndex=0)

    undefinedundefined

    let word1 = regexp.exec(str); alert(word1[0]); // let (1st word) alert(regexp.lastIndex); // 3 (position after the match)

    undefinedundefined

    let word2 = regexp.exec(str); alert(word2[0]); // varName (2nd word) alert(regexp.lastIndex); // 11 (position after the match)

    undefinedundefined

    let word3 = regexp.exec(str); alert(word3); // null (no more matches) alert(regexp.lastIndex); // 0 (resets at search end)

    undefinedundefined

    We can get all matches in the loop:

    undefinedundefined

    run let str = ‘let varName'; let regexp = /+/g;

    undefinedundefined

    let result;

    undefinedundefined

    while (result = regexp.exec(str)) { alert( undefinedundefinedFound ${result[0]} at position ${result.index} ); // Found let at position 0, then // Found varName at position 4 } undefinedundefined

    undefinedundefined

    Such use of undefinedundefinedregexp.exec is an alternative to method undefinedundefinedstr.matchAll, with a bit more control over the process.undefinedundefined

    undefinedundefined

    Let's go back to our task.

    undefinedundefined

    We can manually set undefinedundefinedlastIndex to undefinedundefined4, to start the search from the given position!undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run let str = ‘let varName = "value"';

    undefinedundefined

    let regexp = /+/g; // without flag "g", property lastIndex is ignored

    undefinedundefined

    undefinedundefined! regexp.lastIndex = 4; undefinedundefined/!undefinedundefined

    undefinedundefined

    let word = regexp.exec(str); alert(word); // varName

    undefinedundefined

    Hooray! Problem solved!

    undefinedundefined

    We performed a search of undefinedundefinedpattern:\w+, starting from position undefinedundefinedregexp.lastIndex = 4.undefinedundefined

    undefinedundefined

    The result is correct.

    undefinedundefined

    …But wait, not so fast.

    undefinedundefined

    Please note: the undefinedundefinedregexp.exec call starts searching at position undefinedundefinedlastIndex and then goes further. If there's no word at position undefinedundefinedlastIndex, but it's somewhere after it, then it will be found:undefinedundefined

    undefinedundefined

    run let str = ‘let varName = "value"';

    undefinedundefined

    let regexp = /+/g;

    undefinedundefined

    undefinedundefined! // start the search from position 3 regexp.lastIndex = 3; undefinedundefined/!undefinedundefined

    undefinedundefined

    let word = regexp.exec(str); // found the match at position 4 alert(word[0]); // varName alert(word.index); // 4

    undefinedundefined

    For some tasks, including the lexical analysis, that's just wrong. We need to find a match exactly at the given position at the text, not somewhere after it. And that's what the flag undefinedundefinedy is for.undefinedundefined

    undefinedundefined

    undefinedundefinedThe flag undefinedundefinedpattern:y makes undefinedundefinedregexp.exec to search exactly at position undefinedundefinedlastIndex, not "starting from" it.undefinedundefinedundefinedundefined

    undefinedundefined

    Here's the same search with flag undefinedundefinedpattern:y:undefinedundefined

    undefinedundefined

    run let str = ‘let varName = "value"';

    undefinedundefined

    let regexp = /+/y;

    undefinedundefined

    regexp.lastIndex = 3; alert( regexp.exec(str) ); // null (there's a space at position 3, not a word)

    undefinedundefined

    regexp.lastIndex = 4; alert( regexp.exec(str) ); // varName (word at position 4)

    undefinedundefined

    As we can see, regexp undefinedundefinedpattern:/\w+/y doesn't match at position undefinedundefined3 (unlike the flag undefinedundefinedpattern:g), but matches at position undefinedundefined4.undefinedundefined

    undefinedundefined

    Not only that's what we need, there's an important performance gain when using flag undefinedundefinedpattern:y.undefinedundefined

    undefinedundefined

    Imagine, we have a long text, and there are no matches in it, at all. Then a search with flag undefinedundefinedpattern:g will go till the end of the text and find nothing, and this will take significantly more time than the search with flag undefinedundefinedpattern:y, that checks only the exact position.undefinedundefined

    undefinedundefined

    In tasks like lexical analysis, there are usually many searches at an exact position, to check what we have there. Using flag undefinedundefinedpattern:y is the key for correct implementations and a good performance.undefinedundefined

    undefinedundefined

    Methods of RegExp and String

    undefinedundefined

    In this article we'll cover various methods that work with regexps in-depth.

    undefinedundefined

    str.match(regexp)

    undefinedundefined

    The method undefinedundefinedstr.match(regexp) finds matches for undefinedundefinedregexp in the string undefinedundefinedstr.undefinedundefined

    undefinedundefined

    It has 3 modes:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      If the undefinedundefinedregexp doesn't have flag undefinedundefinedpattern:g, then it returns the first match as an array with capturing groups and properties undefinedundefinedindex (position of the match), undefinedundefinedinput (input string, equals undefinedundefinedstr):undefinedundefined

      undefinedundefined

      run let str = "I love JavaScript";

      undefinedundefined

      let result = str.match(/Java(Script)/);

      undefinedundefined

      alert( result[0] ); // JavaScript (full match) alert( result[1] ); // Script (first capturing group) alert( result.length ); // 2

      undefinedundefined

      // Additional information: alert( result.index ); // 7 (match position) alert( result.input ); // I love JavaScript (source string)

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      If the undefinedundefinedregexp has flag undefinedundefinedpattern:g, then it returns an array of all matches as strings, without capturing groups and other details. run let str = "I love JavaScript";undefinedundefined

      undefinedundefined

      let result = str.match(/Java(Script)/g);

      undefinedundefined

      alert( result[0] ); // JavaScript alert( result.length ); // 1

      undefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      If there are no matches, no matter if there's flag undefinedundefinedpattern:g or not, undefinedundefinednull is returned.undefinedundefined

      undefinedundefined

      That's an important nuance. If there are no matches, we don't get an empty array, but undefinedundefinednull. It's easy to make a mistake forgetting about it, e.g.:undefinedundefined

      undefinedundefined

      run let str = "I love JavaScript";

      undefinedundefined

      let result = str.match(/HTML/);

      undefinedundefined

      alert(result); // null alert(result.length); // Error: Cannot read property ‘length' of null

      undefinedundefined

      If we want the result to be an array, we can write like this:

      undefinedundefined
      undefinedundefined
      undefinedundefinedundefinedundefinedundefinedundefinedlet result undefinedundefined=undefinedundefinedstr.undefinedundefinedmatch(regexp) undefinedundefined|| []undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
      undefinedundefined
      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    str.matchAll(regexp)

    undefinedundefined

    [recent browser="new"]

    undefinedundefined

    The method undefinedundefinedstr.matchAll(regexp) is a "newer, improved" variant of undefinedundefinedstr.match.undefinedundefined

    undefinedundefined

    It's used mainly to search for all matches with all groups.

    undefinedundefined

    There are 3 differences from undefinedundefinedmatch:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. It returns an iterable object with matches instead of an array. We can make a regular array from it using undefinedundefinedArray.from.undefinedundefined
    2. undefinedundefined
    3. Every match is returned as an array with capturing groups (the same format as undefinedundefinedstr.match without flag undefinedundefinedpattern:g).undefinedundefined
    4. undefinedundefined
    5. If there are no results, it returns not undefinedundefinednull, but an empty iterable object.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Usage example:

    run let str = ''' undefinedundefined

    Hello, world!

    undefinedundefined

    '''; let regexp = /<(.*?)>/g;

    undefinedundefined

    let matchAll = str.matchAll(regexp);

    undefinedundefined

    alert(matchAll); // [object RegExp String Iterator], not array, but an iterable

    undefinedundefined

    matchAll = Array.from(matchAll); // array now

    let firstMatch = matchAll[0]; alert( firstMatch[0] ); // undefinedundefined

    alert( firstMatch[1] ); // h1 alert( firstMatch.index ); // 0 alert( firstMatch.input ); // undefinedundefined

    Hello, world!

    undefinedundefined

    undefinedundefined

    If we use undefinedundefinedfor..of to loop over undefinedundefinedmatchAll matches, then we don't need undefinedundefinedArray.from any more.undefinedundefined

    undefinedundefined

    str.split(regexp|substr, limit)

    undefinedundefined

    Splits the string using the regexp (or a substring) as a delimiter.

    undefinedundefined

    We can use undefinedundefinedsplit with strings, like this:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert('12-34-56'.split('-')) // array of ['12', '34', '56']undefinedundefined

    undefinedundefined

    But we can split by a regular expression, the same way:

    undefinedundefined

    undefinedundefinedjs run alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56']undefinedundefined

    undefinedundefined

    str.search(regexp)

    undefinedundefined

    The method undefinedundefinedstr.search(regexp) returns the position of the first match or undefinedundefined-1 if none found:undefinedundefined

    undefinedundefined

    run let str = "A drop of ink may make a million think";

    undefinedundefined

    alert( str.search( /ink/i ) ); // 10 (first match position)

    undefinedundefined

    undefinedundefinedThe important limitation: undefinedundefinedsearch only finds the first match.undefinedundefinedundefinedundefined

    undefinedundefined

    If we need positions of further matches, we should use other means, such as finding them all with undefinedundefinedstr.matchAll(regexp).undefinedundefined

    undefinedundefined

    str.replace(str|regexp, str|func)

    undefinedundefined

    This is a generic method for searching and replacing, one of most useful ones. The swiss army knife for searching and replacing.

    undefinedundefined

    We can use it without regexps, to search and replace a substring:

    undefinedundefined

    undefinedundefinedjs run // replace a dash by a colon alert('12-34-56'.replace("-", ":")) // 12:34-56undefinedundefined

    undefinedundefined

    There's a pitfall though.

    undefinedundefined

    undefinedundefinedWhen the first argument of undefinedundefinedreplace is a string, it only replaces the first match.undefinedundefinedundefinedundefined

    undefinedundefined

    You can see that in the example above: only the first undefinedundefined"-" is replaced by undefinedundefined":".undefinedundefined

    undefinedundefined

    To find all hyphens, we need to use not the string undefinedundefined"-", but a regexp undefinedundefinedpattern:/-/g, with the obligatory undefinedundefinedpattern:g flag:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // replace all dashes by a colon alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56undefinedundefined

    undefinedundefined

    The second argument is a replacement string. We can use special characters in it:

    undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined
    SymbolsAction in the replacement string
    undefinedundefined$&undefinedundefined inserts the whole match
    undefinedundefinedundefinedundefined$&#096;</code>|inserts a part of the string before the match| |`$'undefinedundefined|inserts a part of the string after the match| |undefinedundefined$n`|if `n` is a 1-2 digit number, inserts the contents of n-th capturing group, for details see [](info:regexp-groups)| |`$undefinedundefined undefinedundefined|inserts the contents of the parentheses with the givennameundefinedundefined, for details see [](info:regexp-groups)| |$undefinedundefined$`|inserts character `$`undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run let str = "John Smith";

    undefinedundefined

    // swap first and last name alert(str.replace(/(john) (smith)/i, ‘$2, $1')) // Smith, John

    undefinedundefined

    undefinedundefinedFor situations that require "smart" replacements, the second argument can be a function.undefinedundefined

    undefinedundefined

    It will be called for each match, and the returned value will be inserted as a replacement.

    undefinedundefined

    The function is called with arguments undefinedundefinedfunc(match, p1, p2, ..., pn, offset, input, groups):undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedmatch - the match,undefinedundefined
    2. undefinedundefined
    3. undefinedundefinedp1, p2, ..., pn - contents of capturing groups (if there are any),undefinedundefined
    4. undefinedundefined
    5. undefinedundefinedoffset - position of the match,undefinedundefined
    6. undefinedundefined
    7. undefinedundefinedinput - the source string,undefinedundefined
    8. undefinedundefined
    9. undefinedundefinedgroups - an object with named groups.undefinedundefined
    10. undefinedundefined
    undefinedundefined

    If there are no parentheses in the regexp, then there are only 3 arguments: undefinedundefinedfunc(str, offset, input).undefinedundefined

    undefinedundefined

    For example, let's uppercase all matches:

    undefinedundefined

    run let str = "html and css";

    undefinedundefined

    let result = str.replace(/html|css/gi, str => str.toUpperCase());

    undefinedundefined

    alert(result); // HTML and CSS

    undefinedundefined

    Replace each match by its position in the string:

    undefinedundefined

    undefinedundefinedjs run alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6undefinedundefined

    undefinedundefined

    In the example below there are two parentheses, so the replacement function is called with 5 arguments: the first is the full match, then 2 parentheses, and after it (not used in the example) the match position and the source string:

    undefinedundefined

    run let str = "John Smith";

    undefinedundefined

    let result = str.replace(/(+) (+)/, (match, name, surname) => undefinedundefined${surname}, ${name});undefinedundefined

    undefinedundefined

    alert(result); // Smith, John

    undefinedundefined

    If there are many groups, it's convenient to use rest parameters to access them:

    undefinedundefined

    run let str = "John Smith";

    undefinedundefined

    let result = str.replace(/(+) (+)/, (…match) => undefinedundefined${match[2]}, ${match[1]});undefinedundefined

    undefinedundefined

    alert(result); // Smith, John

    undefinedundefined

    Or, if we're using named groups, then undefinedundefinedgroups object with them is always the last, so we can obtain it like this:undefinedundefined

    undefinedundefined

    run let str = "John Smith";

    undefinedundefined

    let result = str.replace(/(?undefinedundefined+) (?undefinedundefined +)/, (…match) => { let groups = match.pop();undefinedundefined

    undefinedundefined

    return undefinedundefined${groups.surname}, ${groups.name}; });undefinedundefined

    undefinedundefined

    alert(result); // Smith, John

    undefinedundefined

    Using a function gives us the ultimate replacement power, because it gets all the information about the match, has access to outer variables and can do everything.

    undefinedundefined

    str.replaceAll(str|regexp, str|func)

    undefinedundefined

    This method is essentially the same as undefinedundefinedstr.replace, with two major differences:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. If the first argument is a string, it replaces undefinedundefinedall occurences of the string, while undefinedundefinedreplace replaces only the undefinedundefinedfirst occurence.undefinedundefined
    2. undefinedundefined
    3. If the first argument is a regular expression without the undefinedundefinedg flag, there'll be an error. With undefinedundefinedg flag, it works the same as undefinedundefinedreplace.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    The main use case for undefinedundefinedreplaceAll is replacing all occurences of a string.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    undefinedundefinedjs run // replace all dashes by a colon alert('12-34-56'.replaceAll("-", ":")) // 12:34:56undefinedundefined

    undefinedundefined

    regexp.exec(str)

    undefinedundefined

    The method undefinedundefinedregexp.exec(str) method returns a match for undefinedundefinedregexp in the string undefinedundefinedstr. Unlike previous methods, it's called on a regexp, not on a string.undefinedundefined

    undefinedundefined

    It behaves differently depending on whether the regexp has flag undefinedundefinedpattern:g.undefinedundefined

    undefinedundefined

    If there's no undefinedundefinedpattern:g, then undefinedundefinedregexp.exec(str) returns the first match exactly as undefinedundefinedstr.match(regexp). This behavior doesn't bring anything new.undefinedundefined

    undefinedundefined

    But if there's flag undefinedundefinedpattern:g, then: - A call to undefinedundefinedregexp.exec(str) returns the first match and saves the position immediately after it in the property undefinedundefinedregexp.lastIndex. - The next such call starts the search from position undefinedundefinedregexp.lastIndex, returns the next match and saves the position after it in undefinedundefinedregexp.lastIndex. - …And so on. - If there are no matches, undefinedundefinedregexp.exec returns undefinedundefinednull and resets undefinedundefinedregexp.lastIndex to undefinedundefined0.undefinedundefined

    undefinedundefined

    So, repeated calls return all matches one after another, using property undefinedundefinedregexp.lastIndex to keep track of the current search position.undefinedundefined

    undefinedundefined

    In the past, before the method undefinedundefinedstr.matchAll was added to JavaScript, calls of undefinedundefinedregexp.exec were used in the loop to get all matches with groups:undefinedundefined

    undefinedundefined

    run let str = ‘More about JavaScript at https://javascript.info'; let regexp = /javascript/ig;

    undefinedundefined

    let result;

    undefinedundefined

    while (result = regexp.exec(str)) { alert( undefinedundefinedFound ${result[0]} at position ${result.index} ); // Found JavaScript at position 11, then // Found javascript at position 33 } undefinedundefined

    undefinedundefined

    This works now as well, although for newer browsers undefinedundefinedstr.matchAll is usually more convenient.undefinedundefined

    undefinedundefined

    undefinedundefinedWe can use undefinedundefinedregexp.exec to search from a given position by manually setting undefinedundefinedlastIndex.undefinedundefinedundefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let str = ‘Hello, world!''';

    undefinedundefined

    let regexp = /+/g; // without flag "g", lastIndex property is ignored regexp.lastIndex = 5; // search from 5th position (from the comma)

    undefinedundefined

    alert( regexp.exec(str) ); // world

    undefinedundefined

    If the regexp has flag undefinedundefinedpattern:y, then the search will be performed exactly at the position undefinedundefinedregexp.lastIndex, not any further.undefinedundefined

    undefinedundefined

    Let's replace flag undefinedundefinedpattern:g with undefinedundefinedpattern:y in the example above. There will be no matches, as there's no word at position undefinedundefined5:undefinedundefined

    undefinedundefined

    run let str = ‘Hello, world!''';

    undefinedundefined

    let regexp = /+/y; regexp.lastIndex = 5; // search exactly at position 5

    undefinedundefined

    alert( regexp.exec(str) ); // null

    undefinedundefined

    That's convenient for situations when we need to "read" something from the string by a regexp at the exact position, not somewhere further.

    undefinedundefined

    regexp.test(str)

    undefinedundefined

    The method undefinedundefinedregexp.test(str) looks for a match and returns undefinedundefinedtrue/false whether it exists.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let str = "I love JavaScript";

    undefinedundefined

    // these two tests do the same alert( undefinedundefined!/love/iundefinedundefined/!.test(str) ); // true alert( str.search(undefinedundefined!/love/iundefinedundefined/!) != -1 ); // true undefinedundefined

    undefinedundefined

    An example with the negative answer:

    undefinedundefined

    run let str = "Bla-bla-bla";

    undefinedundefined

    alert( undefinedundefined!/love/iundefinedundefined/!.test(str) ); // false alert( str.search(undefinedundefined!/love/iundefinedundefined/!) != -1 ); // false undefinedundefined

    undefinedundefined

    If the regexp has flag undefinedundefinedpattern:g, then undefinedundefinedregexp.test looks from undefinedundefinedregexp.lastIndex property and updates this property, just like undefinedundefinedregexp.exec.undefinedundefined

    undefinedundefined

    So we can use it to search from a given position:

    undefinedundefined

    run let regexp = /love/gi;

    undefinedundefined

    let str = "I love JavaScript";

    undefinedundefined

    // start the search from position 10: regexp.lastIndex = 10; alert( regexp.test(str) ); // false (no match)

    undefinedundefined

    undefinedundefinedwarn header="Same global regexp tested repeatedly on different sources may fail" If we apply the same global regexp to different inputs, it may lead to wrong result, becauseregexp.testundefinedundefinedcall advancesregexp.lastIndex` property, so the search in another string may start from non-zero position.undefinedundefined

    undefinedundefined

    For instance, here we call undefinedundefinedregexp.test twice on the same text, and the second time fails:undefinedundefined

    undefinedundefined

    run let regexp = /javascript/g; // (regexp just created: regexp.lastIndex=0)

    undefinedundefined

    alert( regexp.test("javascript") ); // true (regexp.lastIndex=10 now) alert( regexp.test("javascript") ); // false

    undefinedundefined
    undefinedundefined
    That's exactly because `regexp.lastIndex` is non-zero in the second test.
    To work around that, we can set `regexp.lastIndex = 0` before each search. Or instead of calling methods on regexp, use string methods `str.match/search/...`, they don't use `lastIndex`.undefinedundefined
    undefinedundefined

    The "switch" statement

    undefinedundefined

    A undefinedundefinedswitch statement can replace multiple undefinedundefinedif checks.undefinedundefined

    undefinedundefined

    It gives a more descriptive way to compare a value with multiple variants.

    undefinedundefined

    The syntax

    undefinedundefined

    The undefinedundefinedswitch has one or more undefinedundefinedcase blocks and an optional default.undefinedundefined

    undefinedundefined

    It looks like this:

    undefinedundefined

    no-beautify switch(x) { case ‘value1': // if (x === ‘value1') … [break]

    undefinedundefined

    case ‘value2': // if (x === ‘value2') … [break]

    undefinedundefined

    default: … [break] }

    undefinedundefined
      undefinedundefined
    • The value of undefinedundefinedx is checked for a strict equality to the value from the first undefinedundefinedcase (that is, undefinedundefinedvalue1) then to the second (undefinedundefinedvalue2) and so on.undefinedundefined
    • undefinedundefined
    • If the equality is found, undefinedundefinedswitch starts to execute the code starting from the corresponding undefinedundefinedcase, until the nearest undefinedundefinedbreak (or until the end of undefinedundefinedswitch).undefinedundefined
    • undefinedundefined
    • If no case is matched then the undefinedundefineddefault code is executed (if it exists).undefinedundefined
    • undefinedundefined
    undefinedundefined

    An example

    undefinedundefined

    An example of undefinedundefinedswitch (the executed code is highlighted):undefinedundefined

    undefinedundefined

    run let a = 2 + 2;

    undefinedundefined

    switch (a) { case 3: alert( ‘Too small' ); break; undefinedundefined! case 4: alert( ‘Exactly!''' ); break; undefinedundefined/! case 5: alert( ‘Too big' ); break; default: alert( "I don't know such values" ); } undefinedundefined

    undefinedundefined

    Here the undefinedundefinedswitch starts to compare undefinedundefineda from the first undefinedundefinedcase variant that is undefinedundefined3. The match fails.undefinedundefined

    undefinedundefined

    Then undefinedundefined4. That's a match, so the execution starts from undefinedundefinedcase 4 until the nearest undefinedundefinedbreak.undefinedundefined

    undefinedundefined

    undefinedundefinedIf there is no undefinedundefinedbreak then the execution continues with the next undefinedundefinedcase without any checks.undefinedundefinedundefinedundefined

    undefinedundefined

    An example without undefinedundefinedbreak:undefinedundefined

    undefinedundefined

    run let a = 2 + 2;

    undefinedundefined

    switch (a) { case 3: alert( ‘Too small' ); undefinedundefined! case 4: alert( ‘Exactly!''' ); case 5: alert( ‘Too big' ); default: alert( "I don't know such values" ); undefinedundefined/! } undefinedundefined

    undefinedundefined

    In the example above we'll see sequential execution of three undefinedundefinedalerts:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefinedsmart header="Any expression can be aswitch/caseundefinedundefinedargument" Bothswitchundefinedundefinedandcase` allow arbitrary expressions.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefined

    run let a = "1"; let b = 0;

    undefinedundefined

    switch (+a) { undefinedundefined! case b + 1: alert("this runs, because +a is 1, exactly equals b+1"); break; undefinedundefined/!undefinedundefined

    undefinedundefined

    default: alert("this doesn't run"); }

    undefinedundefined
    undefinedundefinedHere `+a` gives `1`, that's compared with `b + 1` in `case`, and the corresponding code is executed.undefinedundefined
    undefinedundefined

    Grouping of "case"

    undefinedundefined

    Several variants of undefinedundefinedcase which share the same code can be grouped.undefinedundefined

    undefinedundefined

    For example, if we want the same code to run for undefinedundefinedcase 3 and undefinedundefinedcase 5:undefinedundefined

    undefinedundefined

    run no-beautify let a = 3;

    undefinedundefined

    switch (a) { case 4: alert(‘Right!'''); break;

    undefinedundefined

    undefinedundefined! case 3: // (undefinedundefined) grouped two cases case 5: alert(‘Wrong!'''); alert("Why don't you take a math class?"); break; /!*undefinedundefined

    undefinedundefined

    default: alert(‘The result is strange. Really.'''); }

    undefinedundefined

    Now both undefinedundefined3 and undefinedundefined5 show the same message.undefinedundefined

    undefinedundefined

    The ability to "group" cases is a side-effect of how undefinedundefinedswitch/case works without undefinedundefinedbreak. Here the execution of undefinedundefinedcase 3 starts from the line undefinedundefined(*) and goes through undefinedundefinedcase 5, because there's no undefinedundefinedbreak.undefinedundefined

    undefinedundefined

    Type matters

    undefinedundefined

    Let's emphasize that the equality check is always strict. The values must be of the same type to match.

    undefinedundefined

    For example, let's consider the code:

    undefinedundefined

    run let arg = prompt("Enter a value?"); switch (arg) { case ‘0': case ‘1': alert( ‘One or zero' ); break;

    undefinedundefined

    case ‘2': alert( ‘Two' ); break;

    undefinedundefined

    case 3: alert( ‘Never executes!''' ); break; default: alert( ‘An unknown value' ); }

    undefinedundefined
      undefinedundefined
    1. For undefinedundefined0, undefinedundefined1, the first undefinedundefinedalert runs.undefinedundefined
    2. undefinedundefined
    3. For undefinedundefined2 the second undefinedundefinedalert runs.undefinedundefined
    4. undefinedundefined
    5. But for undefinedundefined3, the result of the undefinedundefinedprompt is a string undefinedundefined"3", which is not strictly equal undefinedundefined=== to the number undefinedundefined3. So we've got a dead code in undefinedundefinedcase 3! The undefinedundefineddefault variant will execute.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Functions

    undefinedundefined

    Quite often we need to perform a similar action in many places of the script.

    undefinedundefined

    For example, we need to show a nice-looking message when a visitor logs in, logs out and maybe somewhere else.

    undefinedundefined

    Functions are the main "building blocks" of the program. They allow the code to be called many times without repetition.

    undefinedundefined

    We've already seen examples of built-in functions, like undefinedundefinedalert(message), undefinedundefinedprompt(message, default) and undefinedundefinedconfirm(question). But we can create functions of our own as well.undefinedundefined

    undefinedundefined

    Function Declaration

    undefinedundefined

    To create a function we can use a undefinedundefinedfunction declaration.undefinedundefined

    undefinedundefined

    It looks like this:

    undefinedundefinedundefinedundefined

    The undefinedundefinedfunction keyword goes first, then goes the undefinedundefinedname of the function, then a list of undefinedundefinedparameters between the parentheses (comma-separated, empty in the example above) and finally the code of the function, also named "the function body", between curly braces.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedname(parameters) undefinedundefined{undefinedundefinedundefinedundefined  ...undefinedundefinedbody...undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Our new function can be called by its name: undefinedundefinedshowMessage().undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run function showMessage() { alert( ‘Hello everyone!''' ); }

    undefinedundefined

    undefinedundefined! showMessage(); showMessage(); undefinedundefined/! undefinedundefined

    undefinedundefined

    The call undefinedundefinedshowMessage() executes the code of the function. Here we will see the message two times.undefinedundefined

    undefinedundefined

    This example clearly demonstrates one of the main purposes of functions: to avoid code duplication.

    undefinedundefined

    If we ever need to change the message or the way it is shown, it's enough to modify the code in one place: the function which outputs it.

    undefinedundefined

    Local variables

    undefinedundefined

    A variable declared inside a function is only visible inside that function.

    undefinedundefined

    For example:

    undefinedundefined

    run function showMessage() { undefinedundefined! let message = "Hello, I'm JavaScript!"; // local variable undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( message ); }

    undefinedundefined

    showMessage(); // Hello, I'm JavaScript!

    undefinedundefined

    alert( message ); // <- Error! The variable is local to the function

    undefinedundefined

    Outer variables

    undefinedundefined

    A function can access an outer variable as well, for example:

    undefinedundefined

    run no-beautify let undefinedundefined!userNameundefinedundefined/! = ‘John';undefinedundefined

    undefinedundefined

    function showMessage() { let message = ‘Hello,''' + undefinedundefined!userNameundefinedundefined/!; alert(message); }undefinedundefined

    undefinedundefined

    showMessage(); // Hello, John

    undefinedundefined

    The function has full access to the outer variable. It can modify it as well.

    undefinedundefined

    For instance:

    undefinedundefined

    run let undefinedundefined!userNameundefinedundefined/! = ‘John';undefinedundefined

    undefinedundefined

    function showMessage() { undefinedundefined!userNameundefinedundefined/! = "Bob"; // (1) changed the outer variableundefinedundefined

    undefinedundefined

    let message = ‘Hello,''' + undefinedundefined!userNameundefinedundefined/!; alert(message); }undefinedundefined

    undefinedundefined

    alert( userName ); // undefinedundefined!Johnundefinedundefined/! before the function callundefinedundefined

    undefinedundefined

    showMessage();

    undefinedundefined

    alert( userName ); // undefinedundefined!Bobundefinedundefined/!, the value was modified by the function undefinedundefined

    undefinedundefined

    The outer variable is only used if there's no local one.

    undefinedundefined

    If a same-named variable is declared inside the function then it undefinedundefinedshadows the outer one. For instance, in the code below the function uses the local undefinedundefineduserName. The outer one is ignored:undefinedundefined

    undefinedundefined

    run let userName = ‘John';

    undefinedundefined

    function showMessage() { undefinedundefined! let userName = "Bob"; // declare a local variable undefinedundefined/!undefinedundefined

    undefinedundefined

    let message = ‘Hello,''' + userName; // undefinedundefined!Bobundefinedundefined/! alert(message); }undefinedundefined

    undefinedundefined

    // the function will create and use its own userName showMessage();

    undefinedundefined

    alert( userName ); // undefinedundefined!Johnundefinedundefined/!, unchanged, the function did not access the outer variable undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Global variables" Variables declared outside of any function, such as the outeruserName` in the code above, are called undefinedundefinedglobal.undefinedundefined

    undefinedundefined

    Global variables are visible from any function (unless shadowed by locals).

    undefinedundefined

    It's a good practice to minimize the use of global variables. Modern code has few or no globals. Most variables reside in their functions. Sometimes though, they can be useful to store project-level data.

    undefinedundefined

    Parameters

    undefinedundefined

    We can pass arbitrary data to functions using parameters (also called undefinedundefinedfunction arguments) .undefinedundefined

    undefinedundefined

    In the example below, the function has two parameters: undefinedundefinedfrom and undefinedundefinedtext.undefinedundefined

    undefinedundefined

    run function showMessage(undefinedundefined!from, textundefinedundefined/!) { // arguments: from, text alert(from + ‘:''' + text); }undefinedundefined

    undefinedundefined

    undefinedundefined! showMessage(‘Ann', ‘Hello!'''); // Ann: Hello! (*) showMessage(‘Ann', "What's up?"); // Ann: What's up? (**) undefinedundefined/! undefinedundefined

    undefinedundefined

    When the function is called in lines undefinedundefined(*) and undefinedundefined(**), the given values are copied to local variables undefinedundefinedfrom and undefinedundefinedtext. Then the function uses them.undefinedundefined

    undefinedundefined

    Here's one more example: we have a variable undefinedundefinedfrom and pass it to the function. Please note: the function changes undefinedundefinedfrom, but the change is not seen outside, because a function always gets a copy of the value:undefinedundefined

    undefinedundefined

    run function showMessage(from, text) {

    undefinedundefined

    undefinedundefined! from = ‘undefinedundefined''' + from + ''''''; // make "from" look nicer undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( from + ‘:''' + text ); }

    undefinedundefined

    let from = "Ann";

    undefinedundefined

    showMessage(from, "Hello"); // undefinedundefinedAnn: Helloundefinedundefined

    undefinedundefined

    // the value of "from" is the same, the function modified a local copy alert( from ); // Ann

    undefinedundefined

    Default values

    undefinedundefined

    If a parameter is not provided, then its value becomes undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    For instance, the aforementioned function undefinedundefinedshowMessage(from, text) can be called with a single argument:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedshowMessage(undefinedundefined"Ann")undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    That's not an error. Such a call would output undefinedundefined"*Ann*: undefined". There's no undefinedundefinedtext, so it's assumed that undefinedundefinedtext === undefined.undefinedundefined

    undefinedundefined

    If we want to use a "default" undefinedundefinedtext in this case, then we can specify it after undefinedundefined=:undefinedundefined

    undefinedundefined

    run function showMessage(from, undefinedundefined!text = "no text given"undefinedundefined/!) { alert( from + ":" + text ); }undefinedundefined

    undefinedundefined

    showMessage("Ann"); // Ann: no text given

    undefinedundefined

    Now if the undefinedundefinedtext parameter is not passed, it will get the value undefinedundefined"no text given"undefinedundefined

    undefinedundefined

    Here undefinedundefined"no text given" is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run function showMessage(from, text = anotherFunction()) { // anotherFunction() only executed if no text given // its result becomes the value of text }undefinedundefined

    undefinedundefined

    smart header="Evaluation of default parameters" In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter.

    undefinedundefined

    In the example above, undefinedundefinedanotherFunction() is called every time undefinedundefinedshowMessage() is called without the undefinedundefinedtext parameter. undefinedundefined

    undefinedundefined

    Alternative default parameters

    undefinedundefined

    Sometimes it makes sense to set default values for parameters not in the function declaration, but at a later stage, during its execution.

    undefinedundefined

    To check for an omitted parameter, we can compare it with undefinedundefinedundefined:undefinedundefined

    undefinedundefined

    run function showMessage(text) { undefinedundefined! if (text === undefined) { text = ‘empty message'; } undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(text); }

    undefinedundefined

    showMessage(); // empty message

    undefinedundefined

    …Or we could use the undefinedundefined|| operator:undefinedundefined

    undefinedundefinedundefinedundefined

    Modern JavaScript engines support the undefinedundefinednullish coalescing operatorundefinedundefined??, it's better when falsy values, such as undefinedundefined0, are considered regular:undefinedundefined

    undefinedundefined

    run // if there's no "count" parameter, show "unknown" function showCount(count) { alert(count ?? "unknown"); }

    undefinedundefined

    showCount(0); // 0 showCount(null); // unknown showCount(); // unknown

    undefinedundefined

    Returning a value

    undefinedundefined

    A function can return a value back into the calling code as the result.

    undefinedundefined

    The simplest example would be a function that sums two values:

    undefinedundefined

    run no-beautify function sum(a, b) { undefinedundefined!returnundefinedundefined/! a + b; }undefinedundefined

    undefinedundefined

    let result = sum(1, 2); alert( result ); // 3

    undefinedundefined

    The directive undefinedundefinedreturn can be in any place of the function. When the execution reaches it, the function stops, and the value is returned to the calling code (assigned to undefinedundefinedresult above).undefinedundefined

    undefinedundefined

    There may be many occurrences of undefinedundefinedreturn in a single function. For instance:undefinedundefined

    undefinedundefined

    run function checkAge(age) { if (age >= 18) { undefinedundefined! return true; undefinedundefined/! } else { undefinedundefined! return confirm(‘Do you have permission from your parents?'''); undefinedundefined/! } }undefinedundefined

    undefinedundefined

    let age = prompt(‘How old are you?''', 18);

    undefinedundefined

    if ( checkAge(age) ) { alert( ‘Access granted' ); } else { alert( ‘Access denied' ); }

    undefinedundefined

    It is possible to use undefinedundefinedreturn without a value. That causes the function to exit immediately.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefinedundefinedundefined

    In the code above, if undefinedundefinedcheckAge(age) returns undefinedundefinedfalse, then undefinedundefinedshowMovie won't proceed to the undefinedundefinedalert.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="A function with an emptyreturnundefinedundefinedor without it returnsundefinedundefinedundefined" If a function does not return a value, it is the same as if it returnsundefined`:undefinedundefined

    undefinedundefined

    run function doNothing() { /* empty */ }

    undefinedundefined

    alert( doNothing() === undefined ); // true

    undefinedundefined

    An empty undefinedundefinedreturn is also the same as undefinedundefinedreturn undefined:undefinedundefined

    undefinedundefined

    run function doNothing() { return; }

    undefinedundefined

    alert( doNothing() === undefined ); // true

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="Never add a newline betweenreturnundefinedundefinedand the value" For a long expression inreturn`, it might be tempting to put it on a separate line, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    That doesn't work, because JavaScript assumes a semicolon after undefinedundefinedreturn. That'll work the same as:undefinedundefined

    undefinedundefinedundefinedundefined

    So, it effectively becomes an empty return.

    undefinedundefined

    If we want the returned expression to wrap across multiple lines, we should start it at the same line as undefinedundefinedreturn. Or at least put the opening parentheses there as follows:undefinedundefined

    undefinedundefinedundefinedundefined

    And it will work just as we expect it to.

    undefinedundefined

    Naming a function [#function-naming]

    undefinedundefined

    Functions are actions. So their name is usually a verb. It should be brief, as accurate as possible and describe what the function does, so that someone reading the code gets an indication of what the function does.

    undefinedundefined

    It is a widespread practice to start a function with a verbal prefix which vaguely describes the action. There must be an agreement within the team on the meaning of the prefixes.

    undefinedundefined

    For instance, functions that start with undefinedundefined"show" usually show something.undefinedundefined

    undefinedundefined

    Function starting with…

    undefinedundefined
      undefinedundefined
    • undefinedundefined"get…" - return a value,undefinedundefined
    • undefinedundefined
    • undefinedundefined"calc…" - calculate something,undefinedundefined
    • undefinedundefined
    • undefinedundefined"create…" - create something,undefinedundefined
    • undefinedundefined
    • undefinedundefined"check…" - check something and return a boolean, etc.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Examples of such names:

    undefinedundefined

    undefinedundefinedjs no-beautify showMessage(..) // shows a message getAge(..) // returns the age (gets it somehow) calcSum(..) // calculates a sum and returns the result createForm(..) // creates a form (and usually returns it) checkPermission(..) // checks a permission, returns true/falseundefinedundefined

    undefinedundefined

    With prefixes in place, a glance at a function name gives an understanding what kind of work it does and what kind of value it returns.

    undefinedundefined

    smart header="One function - one action" A function should do exactly what is suggested by its name, no more.

    undefinedundefined

    Two independent actions usually deserve two functions, even if they are usually called together (in that case we can make a 3rd function that calls those two).

    undefinedundefined

    A few examples of breaking this rule:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedgetAge - would be bad if it shows an undefinedundefinedalert with the age (should only get).undefinedundefined
    • undefinedundefined
    • undefinedundefinedcreateForm - would be bad if it modifies the document, adding a form to it (should only create it and return).undefinedundefined
    • undefinedundefined
    • undefinedundefinedcheckPermission - would be bad if it displays the undefinedundefinedaccess granted/denied message (should only perform the check and return the result).undefinedundefined
    • undefinedundefined
    undefinedundefined

    These examples assume common meanings of prefixes. You and your team are free to agree on other meanings, but usually they're not much different. In any case, you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge.

    undefinedundefined

    smart header="Ultrashort function names" Functions that are used undefinedundefinedvery often sometimes have ultrashort names.undefinedundefined

    undefinedundefined

    For example, the undefinedundefinedjQuery framework defines a function with undefinedundefined$. The undefinedundefinedLodash library has its core function named undefinedundefined_.undefinedundefined

    undefinedundefined

    These are exceptions. Generally function names should be concise and descriptive.

    undefinedundefined

    Functions == Comments

    undefinedundefined

    Functions should be short and do exactly one thing. If that thing is big, maybe it's worth it to split the function into a few smaller functions. Sometimes following this rule may not be that easy, but it's definitely a good thing.

    undefinedundefined

    A separate function is not only easier to test and debug - its very existence is a great comment!

    undefinedundefined

    For instance, compare the two functions undefinedundefinedshowPrimes(n) below. Each one outputs undefinedundefinedprime numbers up to undefinedundefinedn.undefinedundefined

    undefinedundefined

    The first variant uses a label:

    undefinedundefinedundefinedundefined

    The second variant uses an additional function undefinedundefinedisPrime(n) to test for primality:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedshowPrimes(n) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfor (undefinedundefinedlet i undefinedundefined=undefinedundefined2undefinedundefined; i undefinedundefined< nundefinedundefined; iundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedif (undefinedundefined!undefinedundefinedisPrime(i)) undefinedundefinedcontinueundefinedundefined;*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined    alertundefinedundefined(undefinedundefinediundefinedundefined)undefinedundefined;  // a primeundefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunction isPrimeundefinedundefined(undefinedundefinednundefinedundefined)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined  for undefinedundefined(undefinedundefinedlet i = 2; i < n; iundefinedundefined++)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined    if undefinedundefined(undefinedundefined n % i == 0undefinedundefined)undefinedundefined return false;undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefinedundefinedundefinedundefinedundefined  return true;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The second variant is easier to understand, isn't it? Instead of the code piece we see a name of the action (undefinedundefinedisPrime). Sometimes people refer to such code as undefinedundefinedself-describing.undefinedundefined

    undefinedundefined

    So, functions can be created even if we don't intend to reuse them. They structure the code and make it readable.

    undefinedundefined

    Summary

    undefinedundefined

    A function declaration looks like this:

    undefinedundefinedundefinedundefined
      undefinedundefined
    • Values passed to a function as parameters are copied to its local variables.
    • undefinedundefined
    • A function may access outer variables. But it works only from inside out. The code outside of the function doesn't see its local variables.
    • undefinedundefined
    • A function can return a value. If it doesn't, then its result is undefinedundefinedundefined.undefinedundefined
    • undefinedundefined
    undefinedundefined

    To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables.

    undefinedundefined

    It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side-effect.

    undefinedundefined

    Function naming:

    undefinedundefined
      undefinedundefined
    • A name should clearly describe what the function does. When we see a function call in the code, a good name instantly gives us an understanding what it does and returns.
    • undefinedundefined
    • A function is an action, so function names are usually verbal.
    • undefinedundefined
    • There exist many well-known function prefixes like undefinedundefinedcreate…, undefinedundefinedshow…, undefinedundefinedget…, undefinedundefinedcheck… and so on. Use them to hint what a function does.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Functions are the main building blocks of scripts. Now we've covered the basics, so we actually can start creating and using them. But that's only the beginning of the path. We are going to return to them many times, going more deeply into their advanced features.

    undefinedundefined

    Manuals and specifications

    undefinedundefined

    This book is a undefinedundefinedtutorial. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other sources.undefinedundefined

    undefinedundefined

    Specification

    undefinedundefined

    undefinedundefinedThe ECMA-262 specification contains the most in-depth, detailed and formalized information about JavaScript. It defines the language.undefinedundefined

    undefinedundefined

    But being that formalized, it's difficult to understand at first. So if you need the most trustworthy source of information about the language details, the specification is the right place. But it's not for everyday use.

    undefinedundefined

    A new specification version is released every year. In-between these releases, the latest specification draft is at undefinedundefinedhttps://tc39.es/ecma262/.undefinedundefined

    undefinedundefined

    To read about new bleeding-edge features, including those that are "almost standard" (so-called "stage 3"), see proposals at undefinedundefinedhttps://github.com/tc39/proposals.undefinedundefined

    undefinedundefined

    Also, if you're developing for the browser, then there are other specifications covered in the undefinedundefinedsecond part of the tutorial.undefinedundefined

    undefinedundefined

    Manuals

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      undefinedundefinedMDN (Mozilla) JavaScript Reference is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc.undefinedundefined

      undefinedundefined

      One can find it at undefinedundefinedhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference.undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. undefinedundefinedhttps://google.com/search?q=MDN+parseInt to search for undefinedundefinedparseInt function.undefinedundefined

    undefinedundefined

    Compatibility tables

    undefinedundefined

    JavaScript is a developing language, new features get added regularly.

    undefinedundefined

    To see their support among browser-based and other engines, see:

    undefinedundefined undefinedundefined

    All these resources are useful in real-life development, as they contain valuable information about language details, their support etc.

    undefinedundefined

    Please remember them (or this page) for the cases when you need in-depth information about a particular feature.

    undefinedundefined

    Function expressions

    undefinedundefined

    In JavaScript, a function is not a "magical language structure", but a special kind of value.

    undefinedundefined

    The syntax that we used before is called a undefinedundefinedFunction Declaration:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedsayHi() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedalert( undefinedundefined"Hello" )undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    There is another syntax for creating a function that is called a undefinedundefinedFunction Expression.undefinedundefined

    undefinedundefined

    It looks like this:

    undefinedundefinedundefinedundefined

    Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable undefinedundefinedsayHi.undefinedundefined

    undefinedundefined

    The meaning of these code samples is the same: "create a function and put it into the variable undefinedundefinedsayHi".undefinedundefined

    undefinedundefined

    We can even print out that value using undefinedundefinedalert:undefinedundefined

    undefinedundefined

    run function sayHi() { alert( "Hello" ); }

    undefinedundefined

    undefinedundefined! alert( sayHi ); // shows the function code undefinedundefined/! undefinedundefined

    undefinedundefined

    Please note that the last line does not run the function, because there are no parentheses after undefinedundefinedsayHi. There are programming languages where any mention of a function name causes its execution, but JavaScript is not like that.undefinedundefined

    undefinedundefined

    In JavaScript, a function is a value, so we can deal with it as a value. The code above shows its string representation, which is the source code.

    undefinedundefined

    Surely, a function is a special value, in the sense that we can call it like undefinedundefinedsayHi().undefinedundefined

    undefinedundefined

    But it's still a value. So we can work with it like with other kinds of values.

    undefinedundefined

    We can copy a function to another variable:

    undefinedundefined

    run no-beautify function sayHi() { // (1) create alert( "Hello" ); }

    undefinedundefined

    let func = sayHi; // (2) copy

    undefinedundefined

    func(); // Hello // (3) run the copy (it works)! sayHi(); // Hello // this still works too (why wouldn't it)

    undefinedundefined

    Here's what happens above in detail:

    undefinedundefined
      undefinedundefined
    1. The Function Declaration undefinedundefined(1) creates the function and puts it into the variable named undefinedundefinedsayHi.undefinedundefined
    2. undefinedundefined
    3. Line undefinedundefined(2) copies it into the variable undefinedundefinedfunc. Please note again: there are no parentheses after undefinedundefinedsayHi. If there were, then undefinedundefinedfunc = sayHi() would write undefinedundefinedthe result of the callundefinedundefinedsayHi() into undefinedundefinedfunc, not undefinedundefinedthe functionundefinedundefinedsayHi itself.undefinedundefined
    4. undefinedundefined
    5. Now the function can be called as both undefinedundefinedsayHi() and undefinedundefinedfunc().undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Note that we could also have used a Function Expression to declare undefinedundefinedsayHi, in the first line:undefinedundefined

    undefinedundefinedundefinedundefined

    Everything would work the same.

    undefinedundefined

    undefinedundefinedsmart header="Why is there a semicolon at the end?" You might wonder, why does Function Expression have a semicolon;` at the end, but Function Declaration does not:undefinedundefined

    undefinedundefinedundefinedundefined

    The answer is simple: - There's no need for undefinedundefined; at the end of code blocks and syntax structures that use them like undefinedundefinedif { ... }, undefinedundefinedfor { }, undefinedundefinedfunction f { } etc. - A Function Expression is used inside the statement: undefinedundefinedlet sayHi = ...;, as a value. It's not a code block, but rather an assignment. The semicolon undefinedundefined; is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement. undefinedundefined

    undefinedundefined

    Callback functions

    undefinedundefined

    Let's look at more examples of passing functions as values and using function expressions.

    undefinedundefined

    We'll write a function undefinedundefinedask(question, yes, no) with three parameters:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedquestionundefinedundefined
    undefinedundefined
    Text of the question
    undefinedundefined
    undefinedundefinedyesundefinedundefined
    undefinedundefined
    Function to run if the answer is "Yes"
    undefinedundefined
    undefinedundefinednoundefinedundefined
    undefinedundefined
    Function to run if the answer is "No"
    undefinedundefined
    undefinedundefined

    The function should ask the undefinedundefinedquestion and, depending on the user's answer, call undefinedundefinedyes() or undefinedundefinedno():undefinedundefined

    undefinedundefined

    run undefinedundefined! function ask(question, yes, no) { if (confirm(question)) yes() else no(); } undefinedundefined/!undefinedundefined

    undefinedundefined

    function showOk() { alert( "You agreed." ); }

    undefinedundefined

    function showCancel() { alert( "You canceled the execution." ); }

    undefinedundefined

    // usage: functions showOk, showCancel are passed as arguments to ask ask("Do you agree?", showOk, showCancel);

    undefinedundefined

    In practice, such functions are quite useful. The major difference between a real-life undefinedundefinedask and the example above is that real-life functions use more complex ways to interact with the user than a simple undefinedundefinedconfirm. In the browser, such function usually draws a nice-looking question window. But that's another story.undefinedundefined

    undefinedundefined

    undefinedundefinedThe arguments undefinedundefinedshowOk and undefinedundefinedshowCancel of undefinedundefinedask are called undefinedundefinedcallback functions or just undefinedundefinedcallbacks.undefinedundefinedundefinedundefined

    undefinedundefined

    The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, undefinedundefinedshowOk becomes the callback for "yes" answer, and undefinedundefinedshowCancel for "no" answer.undefinedundefined

    undefinedundefined

    We can use Function Expressions to write the same function much shorter:

    undefinedundefined

    run no-beautify function ask(question, yes, no) { if (confirm(question)) yes() else no(); }

    undefinedundefined

    undefinedundefined! ask( "Do you agree?", function() { alert("You agreed."); }, function() { alert("You canceled the execution."); } ); undefinedundefined/! undefinedundefined

    undefinedundefined

    Here, functions are declared right inside the undefinedundefinedask(...) call. They have no name, and so are called undefinedundefinedanonymous. Such functions are not accessible outside of undefinedundefinedask (because they are not assigned to variables), but that's just what we want here.undefinedundefined

    undefinedundefined

    Such code appears in our scripts very naturally, it's in the spirit of JavaScript.

    undefinedundefined

    smart header="A function is a value representing an "action"" Regular values like strings or numbers represent the undefinedundefineddata.undefinedundefined

    undefinedundefined

    A function can be perceived as an undefinedundefinedaction.undefinedundefined

    undefinedundefined

    We can pass it between variables and run when we want.

    undefinedundefined

    Function Expression vs Function Declaration

    undefinedundefined

    Let's formulate the key differences between Function Declarations and Expressions.

    undefinedundefined

    First, the syntax: how to differentiate between them in the code.

    undefinedundefined undefinedundefined

    The more subtle difference is undefinedundefinedwhen a function is created by the JavaScript engine.undefinedundefined

    undefinedundefined

    undefinedundefinedA Function Expression is created when the execution reaches it and is usable only from that moment.undefinedundefined

    undefinedundefined

    Once the execution flow passes to the right side of the assignment undefinedundefinedlet sum = function… - here we go, the function is created and can be used (assigned, called, etc. ) from now on.undefinedundefined

    undefinedundefined

    Function Declarations are different.

    undefinedundefined

    undefinedundefinedA Function Declaration can be called earlier than it is defined.undefinedundefined

    undefinedundefined

    For example, a global Function Declaration is visible in the whole script, no matter where it is.

    undefinedundefined

    That's due to internal algorithms. When JavaScript prepares to run the script, it first looks for global Function Declarations in it and creates the functions. We can think of it as an "initialization stage".

    undefinedundefined

    And after all Function Declarations are processed, the code is executed. So it has access to these functions.

    undefinedundefined

    For example, this works:

    undefinedundefined

    run refresh untrusted undefinedundefined! sayHi("John"); // Hello, John undefinedundefined/!undefinedundefined

    undefinedundefined

    function sayHi(name) { alert( undefinedundefinedHello, ${name} ); } undefinedundefined

    undefinedundefined

    The Function Declaration undefinedundefinedsayHi is created when JavaScript is preparing to start the script and is visible everywhere in it.undefinedundefined

    undefinedundefined

    …If it were a Function Expression, then it wouldn't work:

    undefinedundefined

    run refresh untrusted undefinedundefined! sayHi("John"); // error! undefinedundefined/!undefinedundefined

    undefinedundefined

    let sayHi = function(name) { // (*) no magic any more alert( undefinedundefinedHello, ${name} ); }; undefinedundefined

    undefinedundefined

    Function Expressions are created when the execution reaches them. That would happen only in the line undefinedundefined(*). Too late.undefinedundefined

    undefinedundefined

    Another special feature of Function Declarations is their block scope.

    undefinedundefined

    undefinedundefinedIn strict mode, when a Function Declaration is within a code block, it's visible everywhere inside that block. But not outside of it.undefinedundefined

    undefinedundefined

    For instance, let's imagine that we need to declare a function undefinedundefinedwelcome() depending on the undefinedundefinedage variable that we get during runtime. And then we plan to use it some time later.undefinedundefined

    undefinedundefined

    If we use Function Declaration, it won't work as intended:

    undefinedundefined

    run let age = prompt("What is your age?", 18);

    undefinedundefined

    // conditionally declare a function if (age < 18) {

    undefinedundefined

    function welcome() { alert("Hello!"); }

    undefinedundefined

    } else {

    undefinedundefined

    function welcome() { alert("Greetings!"); }

    undefinedundefined

    }

    undefinedundefined

    // …use it later undefinedundefined! welcome(); // Error: welcome is not defined undefinedundefined/! undefinedundefined

    undefinedundefined

    That's because a Function Declaration is only visible inside the code block in which it resides.

    undefinedundefined

    Here's another example:

    undefinedundefined

    run let age = 16; // take 16 as an example

    undefinedundefined

    if (age < 18) { undefinedundefined! welcome(); // (runs) undefinedundefined/! // | function welcome() { // |undefinedundefined
    alert("Hello!"); // | Function Declaration is available } // | everywhere in the block where it's declared // | undefinedundefined! welcome(); // / (runs) undefinedundefined/!undefinedundefined

    undefinedundefined

    } else {

    undefinedundefined

    function welcome() {undefinedundefined
    alert("Greetings!"); } }undefinedundefined

    undefinedundefined

    // Here we're out of curly braces, // so we can not see Function Declarations made inside of them.

    undefinedundefined

    undefinedundefined! welcome(); // Error: welcome is not defined undefinedundefined/! undefinedundefined

    undefinedundefined

    What can we do to make undefinedundefinedwelcome visible outside of undefinedundefinedif?undefinedundefined

    undefinedundefined

    The correct approach would be to use a Function Expression and assign undefinedundefinedwelcome to the variable that is declared outside of undefinedundefinedif and has the proper visibility.undefinedundefined

    undefinedundefined

    This code works as intended:

    undefinedundefined

    run let age = prompt("What is your age?", 18);

    undefinedundefined

    let welcome;

    undefinedundefined

    if (age < 18) {

    undefinedundefined

    welcome = function() { alert("Hello!"); };

    undefinedundefined

    } else {

    undefinedundefined

    welcome = function() { alert("Greetings!"); };

    undefinedundefined

    }

    undefinedundefined

    undefinedundefined! welcome(); // ok now undefinedundefined/! undefinedundefined

    undefinedundefined

    Or we could simplify it even further using a question mark operator undefinedundefined?:undefinedundefined

    undefinedundefined

    run let age = prompt("What is your age?", 18);

    undefinedundefined

    let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); };

    undefinedundefined

    undefinedundefined! welcome(); // ok now undefinedundefined/! undefinedundefined

    undefinedundefined

    smart header="When to choose Function Declaration versus Function Expression?" As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared.

    undefinedundefined

    That's also better for readability, as it's easier to look up undefinedundefinedfunction f(…) {…} in the code than undefinedundefinedlet f = function(…) {…};. Function Declarations are more "eye-catching".undefinedundefined

    undefinedundefined

    …But if a Function Declaration does not suit us for some reason, or we need a conditional declaration (we've just seen an example), then Function Expression should be used.

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Functions are values. They can be assigned, copied or declared in any place of the code.
    • undefinedundefined
    • If the function is declared as a separate statement in the main code flow, that's called a "Function Declaration".
    • undefinedundefined
    • If the function is created as a part of an expression, it's called a "Function Expression".
    • undefinedundefined
    • Function Declarations are processed before the code block is executed. They are visible everywhere in the block.
    • undefinedundefined
    • Function Expressions are created when the execution flow reaches them.
    • undefinedundefined
    undefinedundefined

    In most cases when we need to declare a function, a Function Declaration is preferable, because it is visible prior to the declaration itself. That gives us more flexibility in code organization, and is usually more readable.

    undefinedundefined

    So we should use a Function Expression only when a Function Declaration is not fit for the task. We've seen a couple of examples of that in this chapter, and will see more in the future.

    undefinedundefined

    Arrow functions, the basics

    undefinedundefined

    There's another very simple and concise syntax for creating functions, that's often better than Function Expressions.

    undefinedundefined

    It's called "arrow functions", because it looks like this:

    undefinedundefined undefinedundefined

    …This creates a function undefinedundefinedfunc that accepts arguments undefinedundefinedarg1..argN, then evaluates the undefinedundefinedexpression on the right side with their use and returns its result.undefinedundefined

    undefinedundefined

    In other words, it's the shorter version of:

    undefinedundefinedundefinedundefined

    Let's see a concrete example:

    undefinedundefined

    run let sum = (a, b) => a + b;

    undefinedundefined

    /* This arrow function is a shorter form of:

    undefinedundefined

    let sum = function(a, b) { return a + b; }; */

    undefinedundefined

    alert( sum(1, 2) ); // 3

    undefinedundefined

    As you can, see undefinedundefined(a, b) => a + b means a function that accepts two arguments named undefinedundefineda and undefinedundefinedb. Upon the execution, it evaluates the expression undefinedundefineda + b and returns the result.undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      If we have only one argument, then parentheses around parameters can be omitted, making that even shorter.

      undefinedundefined

      For example:

      undefinedundefined

      run undefinedundefined! let double = n => n * 2; // roughly the same as: let double = function(n) { return n * 2 } undefinedundefined/!undefinedundefined

      undefinedundefined

      alert( double(3) ); // 6

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      If there are no arguments, parentheses will be empty (but they should be present):

      undefinedundefined

      run let sayHi = () => alert("Hello!");

      undefinedundefined

      sayHi();

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Arrow functions can be used in the same way as Function Expressions.

    undefinedundefined

    For instance, to dynamically create a function:

    undefinedundefined

    run let age = prompt("What is your age?", 18);

    undefinedundefined

    let welcome = (age < 18) ? () => alert(‘Hello') : () => alert("Greetings!");

    undefinedundefined

    welcome();

    undefinedundefined

    Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure.

    undefinedundefined

    They are very convenient for simple one-line actions, when we're just too lazy to write many words.

    undefinedundefined

    Multiline arrow functions

    undefinedundefined

    The examples above took arguments from the left of undefinedundefined=> and evaluated the right-side expression with them.undefinedundefined

    undefinedundefined

    Sometimes we need something a little bit more complex, like multiple expressions or statements. It is also possible, but we should enclose them in curly braces. Then use a normal undefinedundefinedreturn within them.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run let sum = (a, b) => { // the curly brace opens a multiline function let result = a + b; undefinedundefined! return result; // if we use curly braces, then we need an explicit "return" undefinedundefined/! };undefinedundefined

    undefinedundefined

    alert( sum(1, 2) ); // 3

    undefinedundefined

    smart header="More to come" Here we praised arrow functions for brevity. But that's not all!

    undefinedundefined

    Arrow functions have other interesting features.

    undefinedundefined

    To study them in-depth, we first need to get to know some other aspects of JavaScript, so we'll return to arrow functions later in the chapter undefinedundefinedinfo:arrow-functions.undefinedundefined

    undefinedundefined

    For now, we can already use arrow functions for one-line actions and callbacks.

    undefinedundefined

    Summary

    undefinedundefined

    Arrow functions are handy for one-liners. They come in two flavors:

    undefinedundefined
      undefinedundefined
    1. Without curly braces: undefinedundefined(...args) => expression - the right side is an expression: the function evaluates it and returns the result.undefinedundefined
    2. undefinedundefined
    3. With curly braces: undefinedundefined(...args) => { body } - brackets allow us to write multiple statements inside the function, but we need an explicit undefinedundefinedreturn to return something.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    JavaScript specials

    undefinedundefined

    This chapter briefly recaps the features of JavaScript that we've learned by now, paying special attention to subtle moments.

    undefinedundefined

    Code structure

    undefinedundefined

    Statements are delimited with a semicolon:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert('Hello'); alert('World');undefinedundefined

    undefinedundefined

    Usually, a line-break is also treated as a delimiter, so that would also work:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert('Hello') alert('World')undefinedundefined

    undefinedundefined

    That's called "automatic semicolon insertion". Sometimes it doesn't work, for instance:

    undefinedundefined

    run alert("There will be an error after this message")

    undefinedundefined

    [1, 2].forEach(alert)

    undefinedundefined

    Most codestyle guides agree that we should put a semicolon after each statement.

    undefinedundefined

    Semicolons are not required after code blocks undefinedundefined{...} and syntax constructs with them like loops:undefinedundefined

    undefinedundefinedundefinedundefined

    …But even if we can put an "extra" semicolon somewhere, that's not an error. It will be ignored.

    undefinedundefined

    More in: undefinedundefinedinfo:structure.undefinedundefined

    undefinedundefined

    Strict mode

    undefinedundefined

    To fully enable all features of modern JavaScript, we should start scripts with undefinedundefined"use strict".undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined'use strict'undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined...undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The directive must be at the top of a script or at the beginning of a function body.

    undefinedundefined

    Without undefinedundefined"use strict", everything still works, but some features behave in the old-fashion, "compatible" way. We'd generally prefer the modern behavior.undefinedundefined

    undefinedundefined

    Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly.

    undefinedundefined

    More in: undefinedundefinedinfo:strict-mode.undefinedundefined

    undefinedundefined

    Variables

    undefinedundefined

    Can be declared using:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedletundefinedundefined
    • undefinedundefined
    • undefinedundefinedconst (constant, can't be changed)undefinedundefined
    • undefinedundefined
    • undefinedundefinedvar (old-style, will see later)undefinedundefined
    • undefinedundefined
    undefinedundefined

    A variable name can include: - Letters and digits, but the first character may not be a digit. - Characters undefinedundefined$ and undefinedundefined_ are normal, on par with letters. - Non-Latin alphabets and hieroglyphs are also allowed, but commonly not used.undefinedundefined

    undefinedundefined

    Variables are dynamically typed. They can store any value:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet x undefinedundefined=undefinedundefined5undefinedundefined;undefinedundefinedundefinedundefinedx undefinedundefined=undefinedundefined"John"undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    There are 8 data types:

    undefinedundefined
      undefinedundefined
    • undefinedundefinednumber for both floating-point and integer numbers,undefinedundefined
    • undefinedundefined
    • undefinedundefinedbigint for integer numbers of arbitrary length,undefinedundefined
    • undefinedundefined
    • undefinedundefinedstring for strings,undefinedundefined
    • undefinedundefined
    • undefinedundefinedboolean for logical values: undefinedundefinedtrue/false,undefinedundefined
    • undefinedundefined
    • undefinedundefinednull - a type with a single value undefinedundefinednull, meaning "empty" or "does not exist",undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefined - a type with a single value undefinedundefinedundefined, meaning "not assigned",undefinedundefined
    • undefinedundefined
    • undefinedundefinedobject and undefinedundefinedsymbol - for complex data structures and unique identifiers, we haven't learnt them yet.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The undefinedundefinedtypeof operator returns the type for a value, with two exceptions:undefinedundefined

    undefinedundefinedundefinedundefined

    More in: undefinedundefinedinfo:variables and undefinedundefinedinfo:types.undefinedundefined

    undefinedundefined

    Interaction

    undefinedundefined

    We're using a browser as a working environment, so basic UI functions will be:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedprompt(question, [default])undefinedundefinedundefinedundefined
    undefinedundefined
    Ask a undefinedundefinedquestion, and return either what the visitor entered or undefinedundefinednull if they clicked "cancel". undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedconfirm(question)undefinedundefinedundefinedundefined
    undefinedundefined
    Ask a undefinedundefinedquestion and suggest to choose between Ok and Cancel. The choice is returned as undefinedundefinedtrue/false. undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedalert(message)undefinedundefinedundefinedundefined
    undefinedundefined
    Output a undefinedundefinedmessage. undefinedundefined
    undefinedundefined
    undefinedundefined

    All these functions are undefinedundefinedmodal, they pause the code execution and prevent the visitor from interacting with the page until they answer.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let userName = prompt("Your name?", "Alice"); let isTeaWanted = confirm("Do you want some tea?");

    undefinedundefined

    alert( "Visitor:" + userName ); // Alice alert( "Tea wanted:" + isTeaWanted ); // true

    undefinedundefined

    More in: undefinedundefinedinfo:alert-prompt-confirm.undefinedundefined

    undefinedundefined

    Operators

    undefinedundefined

    JavaScript supports the following operators:

    undefinedundefined
    undefinedundefined
    Arithmetical
    undefinedundefined
    undefinedundefined

    Regular: undefinedundefined* + - /, also undefinedundefined% for the remainder and undefinedundefined** for power of a number.undefinedundefined

    undefinedundefined

    The binary plus undefinedundefined+ concatenates strings. And if any of the operands is a string, the other one is converted to string too:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( '1' + 2 ); // '12', string alert( 1 + '2' ); // '12', stringundefinedundefined

    undefinedundefined
    undefinedundefined
    Assignments
    undefinedundefined
    There is a simple assignment: undefinedundefineda = b and combined ones like undefinedundefineda *= 2. undefinedundefined
    undefinedundefined
    Bitwise
    undefinedundefined
    Bitwise operators work with 32-bit integers at the lowest, bit-level: see the undefinedundefineddocs when they are needed. undefinedundefined
    undefinedundefined
    Conditional
    undefinedundefined
    The only operator with three parameters: undefinedundefinedcond ? resultA : resultB. If undefinedundefinedcond is truthy, returns undefinedundefinedresultA, otherwise undefinedundefinedresultB. undefinedundefined
    undefinedundefined
    Logical operators
    undefinedundefined
    Logical AND undefinedundefined&& and OR undefinedundefined|| perform short-circuit evaluation and then return the value where it stopped (not necessary undefinedundefinedtrue/undefinedundefinedfalse). Logical NOT undefinedundefined! converts the operand to boolean type and returns the inverse value. undefinedundefined
    undefinedundefined
    Nullish coalescing operator
    undefinedundefined
    The undefinedundefined?? operator provides a way to choose a defined value from a list of variables. The result of undefinedundefineda ?? b is undefinedundefineda unless it's undefinedundefinednull/undefined, then undefinedundefinedb. undefinedundefined
    undefinedundefined
    Comparisons
    undefinedundefined
    undefinedundefined

    Equality check undefinedundefined== for values of different types converts them to a number (except undefinedundefinednull and undefinedundefinedundefined that equal each other and nothing else), so these are equal:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( 0 == false ); // true alert( 0 == '' ); // trueundefinedundefined

    undefinedundefined

    Other comparisons convert to a number as well.

    undefinedundefined

    The strict equality operator undefinedundefined=== doesn't do the conversion: different types always mean different values for it.undefinedundefined

    undefinedundefined

    Values undefinedundefinednull and undefinedundefinedundefined are special: they equal undefinedundefined== each other and don't equal anything else.undefinedundefined

    undefinedundefined

    Greater/less comparisons compare strings character-by-character, other types are converted to a number.

    undefinedundefined
    undefinedundefined
    Other operators
    undefinedundefined
    There are few others, like a comma operator.
    undefinedundefined
    undefinedundefined

    More in: undefinedundefinedinfo:operators, undefinedundefinedinfo:comparison, undefinedundefinedinfo:logical-operators, undefinedundefinedinfo:nullish-coalescing-operator.undefinedundefined

    undefinedundefined

    Loops

    undefinedundefined undefinedundefined

    Details in: undefinedundefinedinfo:while-for.undefinedundefined

    undefinedundefined

    Later we'll study more types of loops to deal with objects.

    undefinedundefined

    The "switch" construct

    undefinedundefined

    The "switch" construct can replace multiple undefinedundefinedif checks. It uses undefinedundefined=== (strict equality) for comparisons.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let age = prompt(‘Your age?''', 18);

    undefinedundefined

    switch (age) { case 18: alert("Won't work"); // the result of prompt is a string, not a number break;

    undefinedundefined

    case "18": alert("This works!"); break;

    undefinedundefined

    default: alert("Any value not equal to one above"); }

    undefinedundefined

    Details in: undefinedundefinedinfo:switch.undefinedundefined

    undefinedundefined

    Functions

    undefinedundefined

    We covered three ways to create a function in JavaScript:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      Function Declaration: the function in the main code flow

      undefinedundefinedundefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Function Expression: the function in the context of an expression

      undefinedundefinedundefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      Arrow functions:

      undefinedundefined undefinedundefined
    6. undefinedundefined
    undefinedundefined
      undefinedundefined
    • Functions may have local variables: those declared inside its body or its parameter list. Such variables are only visible inside the function.
    • undefinedundefined
    • Parameters can have default values: undefinedundefinedfunction sum(a = 1, b = 2) {...}.undefinedundefined
    • undefinedundefined
    • Functions always return something. If there's no undefinedundefinedreturn statement, then the result is undefinedundefinedundefined.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Details: see undefinedundefinedinfo:function-basics, undefinedundefinedinfo:arrow-functions-basics.undefinedundefined

    undefinedundefined

    More to come

    undefinedundefined

    That was a brief list of JavaScript features. As of now we've studied only basics. Further in the tutorial you'll find more specials and advanced features of JavaScript.

    undefinedundefined

    Debugging in Chrome

    undefinedundefined

    Before writing more complex code, let's talk about debugging.

    undefinedundefined

    undefinedundefinedDebugging is the process of finding and fixing errors within a script. All modern browsers and most other environments support debugging tools - a special UI in developer tools that makes debugging much easier. It also allows to trace the code step by step to see what exactly is going on.undefinedundefined

    undefinedundefined

    We'll be using Chrome here, because it has enough features, most other browsers have a similar process.

    undefinedundefined

    The "Sources" panel

    undefinedundefined

    Your Chrome version may look a little bit different, but it still should be obvious what's there.

    undefinedundefined
      undefinedundefined
    • Open the undefinedundefinedexample page in Chrome.undefinedundefined
    • undefinedundefined
    • Turn on developer tools with undefinedundefinedkey:F12 (Mac: undefinedundefinedkey:Cmd+Opt+I).undefinedundefined
    • undefinedundefined
    • Select the undefinedundefinedSources panel.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Here's what you should see if you are doing it for the first time:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The toggler button undefinedundefined opens the tab with files.undefinedundefined

    undefinedundefined

    Let's click it and select undefinedundefinedhello.js in the tree view. Here's what should show up:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The Sources panel has 3 parts:

    undefinedundefined
      undefinedundefined
    1. The undefinedundefinedFile Navigator pane lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too.undefinedundefined
    2. undefinedundefined
    3. The undefinedundefinedCode Editor pane shows the source code.undefinedundefined
    4. undefinedundefined
    5. The undefinedundefinedJavaScript Debugging pane is for debugging, we'll explore it soon.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Now you could click the same toggler undefinedundefined again to hide the resources list and give the code some space.undefinedundefined

    undefinedundefined

    Console

    undefinedundefined

    If we press undefinedundefinedkey:Esc, then a console opens below. We can type commands there and press undefinedundefinedkey:Enter to execute.undefinedundefined

    undefinedundefined

    After a statement is executed, its result is shown below.

    undefinedundefined

    For example, here undefinedundefined1+2 results in undefinedundefined3, and undefinedundefinedhello("debugger") returns nothing, so the result is undefinedundefinedundefined:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Breakpoints

    undefinedundefined

    Let's examine what's going on within the code of the undefinedundefinedexample page. In undefinedundefinedhello.js, click at line number undefinedundefined4. Yes, right on the undefinedundefined4 digit, not on the code.undefinedundefined

    undefinedundefined

    Congratulations! You've set a breakpoint. Please also click on the number for line undefinedundefined8.undefinedundefined

    undefinedundefined

    It should look like this (blue is where you should click):

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    A undefinedundefinedbreakpoint is a point of code where the debugger will automatically pause the JavaScript execution.undefinedundefined

    undefinedundefined

    While the code is paused, we can examine current variables, execute commands in the console etc. In other words, we can debug it.

    undefinedundefined

    We can always find a list of breakpoints in the right panel. That's useful when we have many breakpoints in various files. It allows us to: - Quickly jump to the breakpoint in the code (by clicking on it in the right panel). - Temporarily disable the breakpoint by unchecking it. - Remove the breakpoint by right-clicking and selecting Remove. - …And so on.

    undefinedundefined

    smart header="Conditional breakpoints" undefinedundefinedRight click on the line number allows to create a undefinedundefinedconditional breakpoint. It only triggers when the given expression is truthy.undefinedundefined

    undefinedundefined

    That's handy when we need to stop only for a certain variable value or for certain function parameters.

    undefinedundefined

    Debugger command

    undefinedundefined

    We can also pause the code by using the undefinedundefineddebugger command in it, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    That's very convenient when we are in a code editor and don't want to switch to the browser and look up the script in developer tools to set the breakpoint.

    undefinedundefined

    Pause and look around

    undefinedundefined

    In our example, undefinedundefinedhello() is called during the page load, so the easiest way to activate the debugger (after we've set the breakpoints) is to reload the page. So let's press undefinedundefinedkey:F5 (Windows, Linux) or undefinedundefinedkey:Cmd+R (Mac).undefinedundefined

    undefinedundefined

    As the breakpoint is set, the execution pauses at the 4th line:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Please open the informational dropdowns to the right (labeled with arrows). They allow you to examine the current code state:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      undefinedundefinedundefinedundefinedWatch - shows current values for any expressions.undefinedundefinedundefinedundefined

      undefinedundefined

      You can click the plus undefinedundefined+ and input an expression. The debugger will show its value at any moment, automatically recalculating it in the process of execution.undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      undefinedundefinedundefinedundefinedCall Stack - shows the nested calls chain.undefinedundefinedundefinedundefined

      undefinedundefined

      At the current moment the debugger is inside undefinedundefinedhello() call, called by a script in undefinedundefinedindex.html (no function there, so it's called "anonymous").undefinedundefined

      If you click on a stack item (e.g. "anonymous"), the debugger jumps to the corresponding code, and all its variables can be examined as well.undefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      undefinedundefinedundefinedundefinedScope - current variables.undefinedundefinedundefinedundefined

      undefinedundefined

      undefinedundefinedLocal shows local function variables. You can also see their values highlighted right over the source.undefinedundefined

      undefinedundefined

      undefinedundefinedGlobal has global variables (out of any functions).undefinedundefined

      undefinedundefined

      There's also undefinedundefinedthis keyword there that we didn't study yet, but we'll do that soon.undefinedundefined

      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Tracing the execution

    undefinedundefined

    Now it's time to undefinedundefinedtrace the script.undefinedundefined

    undefinedundefined

    There are buttons for it at the top of the right panel. Let's engage them. undefinedundefined undefinedundefined - "Resume": continue the execution, hotkey undefinedundefinedkey:F8. : Resumes the execution. If there are no additional breakpoints, then the execution just continues and the debugger loses control.undefinedundefined

    undefinedundefined
    undefinedundefinedHere's what we can see after a click on it:
    ![](chrome-sources-debugger-trace-1.svg)
    The execution has resumed, reached another breakpoint inside `say()` and paused there. Take a look at the "Call Stack" at the right. It has increased by one more call. We're inside `say()` now.undefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefined - "Step": run the next command, hotkey undefinedundefinedkey:F9.undefinedundefined
    undefinedundefined
    undefinedundefined

    Run the next statement. If we click it now, undefinedundefinedalert will be shown.undefinedundefined

    undefinedundefined

    Clicking this again and again will step through all script statements one by one.

    undefinedundefined
    undefinedundefined
    undefinedundefined - "Step over": run the next command, but undefinedundefineddon't go into a function, hotkey undefinedundefinedkey:F10.undefinedundefined
    undefinedundefined
    undefinedundefined

    Similar to the previous "Step" command, but behaves differently if the next statement is a function call. That is: not a built-in, like undefinedundefinedalert, but a function of our own.undefinedundefined

    undefinedundefined

    The "Step" command goes into it and pauses the execution at its first line, while "Step over" executes the nested function call invisibly, skipping the function internals.

    undefinedundefined

    The execution is then paused immediately after that function.

    undefinedundefined

    That's good if we're not interested to see what happens inside the function call.

    undefinedundefined
    undefinedundefined
    undefinedundefined - "Step into", hotkey undefinedundefinedkey:F11.undefinedundefined
    undefinedundefined
    undefinedundefined

    That's similar to "Step", but behaves differently in case of asynchronous function calls. If you're only starting to learn JavaScript, then you can ignore the difference, as we don't have asynchronous calls yet.

    undefinedundefined

    For the future, just note that "Step" command ignores async actions, such as undefinedundefinedsetTimeout (scheduled function call), that execute later. The "Step into" goes into their code, waiting for them if necessary. See undefinedundefinedDevTools manual for more details.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined - "Step out": continue the execution till the end of the current function, hotkey undefinedundefinedkey:Shift+F11.undefinedundefined
    undefinedundefined
    Continue the execution and stop it at the very last line of the current function. That's handy when we accidentally entered a nested call using undefinedundefined, but it does not interest us, and we want to continue to its end as soon as possible. undefinedundefined
    undefinedundefined
    undefinedundefined - enable/disable all breakpoints.undefinedundefined
    undefinedundefined
    That button does not move the execution. Just a mass on/off for breakpoints.
    undefinedundefined
    undefinedundefined - enable/disable automatic pause in case of an error.undefinedundefined
    undefinedundefined
    When enabled, and the developer tools is open, a script error automatically pauses the execution. Then we can analyze variables to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment.
    undefinedundefined
    undefinedundefined

    smart header="Continue to here" Right click on a line of code opens the context menu with a great option called "Continue to here".

    undefinedundefined

    That's handy when we want to move multiple steps forward to the line, but we're too lazy to set a breakpoint.

    undefinedundefined

    Logging

    undefinedundefined

    To output something to console from our code, there's undefinedundefinedconsole.log function.undefinedundefined

    undefinedundefined

    For instance, this outputs values from undefinedundefined0 to undefinedundefined4 to console:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // open console to see for (let i = 0; i < 5; i++) { console.log("value,", i); }undefinedundefined

    undefinedundefined

    Regular users don't see that output, it is in the console. To see it, either open the Console panel of developer tools or press undefinedundefinedkey:Esc while in another panel: that opens the console at the bottom.undefinedundefined

    undefinedundefined

    If we have enough logging in our code, then we can see what's going on from the records, without the debugger.

    undefinedundefined

    Summary

    undefinedundefined

    As we can see, there are three main ways to pause a script: 1. A breakpoint. 2. The undefinedundefineddebugger statements. 3. An error (if dev tools are open and the button undefinedundefined is "on").undefinedundefined

    undefinedundefined

    When paused, we can debug - examine variables and trace the code to see where the execution goes wrong.

    undefinedundefined

    There are many more options in developer tools than covered here. The full manual is at undefinedundefinedhttps://developers.google.com/web/tools/chrome-devtools.undefinedundefined

    undefinedundefined

    The information from this chapter is enough to begin debugging, but later, especially if you do a lot of browser stuff, please go there and look through more advanced capabilities of developer tools.

    undefinedundefined

    Oh, and also you can click at various places of dev tools and just see what's showing up. That's probably the fastest route to learn dev tools. Don't forget about the right click and context menus!

    undefinedundefined

    Coding Style

    undefinedundefined

    Our code must be as clean and easy to read as possible.

    undefinedundefined

    That is actually the art of programming - to take a complex task and code it in a way that is both correct and human-readable. A good code style greatly assists in that.

    undefinedundefined

    Syntax

    undefinedundefined

    Here is a cheat sheet with some suggested rules (see below for more details):

    undefinedundefined

    undefinedundefinedundefinedundefined undefinedundefined

    undefinedundefined

    Now let's discuss the rules and reasons for them in detail.

    undefinedundefined

    undefinedundefinedwarn header="There are no \"you must\" rules" Nothing is set in stone here. These are style preferences, not religious dogmas.undefinedundefined

    undefinedundefined

    Curly Braces

    undefinedundefined

    In most JavaScript projects curly braces are written in "Egyptian" style with the opening brace on the same line as the corresponding keyword - not on a new line. There should also be a space before the opening bracket, like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedif (condition) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// do thisundefinedundefinedundefinedundefinedundefinedundefined// ...and thatundefinedundefinedundefinedundefinedundefinedundefined// ...and thatundefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    A single-line construct, such as undefinedundefinedif (condition) doSomething(), is an important edge case. Should we use braces at all?undefinedundefined

    undefinedundefined

    Here are the annotated variants so you can judge their readability for yourself:

    undefinedundefined
      undefinedundefined
    1. 😠 Beginners sometimes do that. Bad! Curly braces are not needed: undefinedundefinedjs if (n < 0) *!*{*/!*alert(`Power ${n} is not supported`);*!*}*/!*undefinedundefined
    2. undefinedundefined
    3. 😠 Split to a separate line without braces. Never do that, easy to make an error when adding new lines: undefinedundefinedjs if (n < 0) alert(`Power ${n} is not supported`);undefinedundefined
    4. undefinedundefined
    5. 😏 One line without braces - acceptable, if it's short: undefinedundefinedjs if (n < 0) alert(`Power ${n} is not supported`);undefinedundefined
    6. undefinedundefined
    7. 😃 The best variant: undefinedundefinedjs if (n < 0) { alert(`Power ${n} is not supported`); }undefinedundefined
    8. undefinedundefined
    undefinedundefined

    For a very brief code, one line is allowed, e.g. undefinedundefinedif (cond) return null. But a code block (the last variant) is usually more readable.undefinedundefined

    undefinedundefined

    Line Length

    undefinedundefined

    No one likes to read a long horizontal line of code. It's best practice to split them.

    undefinedundefined

    For example:

    undefinedundefinedundefinedundefined

    And, for undefinedundefinedif statements:undefinedundefined

    undefinedundefinedundefinedundefined

    The maximum line length should be agreed upon at the team-level. It's usually 80 or 120 characters.

    undefinedundefined

    Indents

    undefinedundefined

    There are two types of indents:

    undefinedundefined undefinedundefined

    Semicolons

    undefinedundefined

    A semicolon should be present after each statement, even if it could possibly be skipped.

    undefinedundefined

    There are languages where a semicolon is truly optional and it is rarely used. In JavaScript, though, there are cases where a line break is not interpreted as a semicolon, leaving the code vulnerable to errors. See more about that in the chapter undefinedundefinedinfo:structure#semicolon.undefinedundefined

    undefinedundefined

    If you're an experienced JavaScript programmer, you may choose a no-semicolon code style like undefinedundefinedStandardJS. Otherwise, it's best to use semicolons to avoid possible pitfalls. The majority of developers put semicolons.undefinedundefined

    undefinedundefined

    Nesting Levels

    undefinedundefined

    Try to avoid nesting code too many levels deep.

    undefinedundefined

    For example, in the loop, it's sometimes a good idea to use the undefinedundefinedundefinedundefinedcontinueundefinedundefined directive to avoid extra nesting.undefinedundefined

    undefinedundefined

    For example, instead of adding a nested undefinedundefinedif conditional like this:undefinedundefined

    undefinedundefinedundefinedundefined

    We can write:

    undefinedundefinedundefinedundefined

    A similar thing can be done with undefinedundefinedif/else and undefinedundefinedreturn.undefinedundefined

    undefinedundefined

    For example, two constructs below are identical.

    undefinedundefined

    Option 1:

    undefinedundefinedundefinedundefined

    Option 2:

    undefinedundefinedundefinedundefined

    The second one is more readable because the "special case" of undefinedundefinedn < 0 is handled early on. Once the check is done we can move on to the "main" code flow without the need for additional nesting.undefinedundefined

    undefinedundefined

    Function Placement

    undefinedundefined

    If you are writing several "helper" functions and the code that uses them, there are three ways to organize the functions.

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      Declare the functions undefinedundefinedabove the code that uses them:undefinedundefined

      undefinedundefinedundefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Code first, then functions

      undefinedundefinedundefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      Mixed: a function is declared where it's first used.

      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Most of time, the second variant is preferred.

    undefinedundefined

    That's because when reading code, we first want to know undefinedundefinedwhat it does. If the code goes first, then it becomes clear from the start. Then, maybe we won't need to read the functions at all, especially if their names are descriptive of what they actually do.undefinedundefined

    undefinedundefined

    Style Guides

    undefinedundefined

    A style guide contains general rules about "how to write" code, e.g. which quotes to use, how many spaces to indent, the maximal line length, etc. A lot of minor things.

    undefinedundefined

    When all members of a team use the same style guide, the code looks uniform, regardless of which team member wrote it.

    undefinedundefined

    Of course, a team can always write their own style guide, but usually there's no need to. There are many existing guides to choose from.

    undefinedundefined

    Some popular choices:

    undefinedundefinedundefinedundefined

    If you're a novice developer, start with the cheat sheet at the beginning of this chapter. Then you can browse other style guides to pick up more ideas and decide which one you like best.

    undefinedundefined

    Automated Linters

    undefinedundefined

    Linters are tools that can automatically check the style of your code and make improving suggestions.

    undefinedundefined

    The great thing about them is that style-checking can also find some bugs, like typos in variable or function names. Because of this feature, using a linter is recommended even if you don't want to stick to one particular "code style".

    undefinedundefined

    Here are some well-known linting tools:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedJSLint - one of the first linters.undefinedundefined
    • undefinedundefined
    • undefinedundefinedJSHint - more settings than JSLint.undefinedundefined
    • undefinedundefined
    • undefinedundefinedESLint - probably the newest one.undefinedundefined
    • undefinedundefined
    undefinedundefined

    All of them can do the job. The author uses undefinedundefinedESLint.undefinedundefined

    undefinedundefined

    Most linters are integrated with many popular editors: just enable the plugin in the editor and configure the style.

    undefinedundefined

    For instance, for ESLint you should do the following:

    undefinedundefined
      undefinedundefined
    1. Install undefinedundefinedNode.js.undefinedundefined
    2. undefinedundefined
    3. Install ESLint with the command undefinedundefinednpm install -g eslint (npm is a JavaScript package installer).undefinedundefined
    4. undefinedundefined
    5. Create a config file named undefinedundefined.eslintrc in the root of your JavaScript project (in the folder that contains all your files).undefinedundefined
    6. undefinedundefined
    7. Install/enable the plugin for your editor that integrates with ESLint. The majority of editors have one.
    8. undefinedundefined
    undefinedundefined

    Here's an example of an undefinedundefined.eslintrc file:undefinedundefined

    undefinedundefinedundefinedundefined

    Here the directive undefinedundefined"extends" denotes that the configuration is based on the "eslint:recommended" set of settings. After that, we specify our own.undefinedundefined

    undefinedundefined

    It is also possible to download style rule sets from the web and extend them instead. See undefinedundefinedhttp://eslint.org/docs/user-guide/getting-started for more details about installation.undefinedundefined

    undefinedundefined

    Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint.

    undefinedundefined

    Summary

    undefinedundefined

    All syntax rules described in this chapter (and in the style guides referenced) aim to increase the readability of your code. All of them are debatable.

    undefinedundefined

    When we think about writing "better" code, the questions we should ask ourselves are: "What makes the code more readable and easier to understand?" and "What can help us avoid errors?" These are the main things to keep in mind when choosing and debating code styles.

    undefinedundefined

    Reading popular style guides will allow you to keep up to date with the latest ideas about code style trends and best practices.

    undefinedundefined

    Comments

    undefinedundefined

    As we know from the chapter undefinedundefinedinfo:structure, comments can be single-line: starting with undefinedundefined// and multiline: undefinedundefined/* ... */.undefinedundefined

    undefinedundefined

    We normally use them to describe how and why the code works.

    undefinedundefined

    At first sight, commenting might be obvious, but novices in programming often use them wrongly.

    undefinedundefined

    Bad comments

    undefinedundefined

    Novices tend to use comments to explain "what is going on in the code". Like this:

    undefinedundefinedundefinedundefined

    But in good code, the amount of such "explanatory" comments should be minimal. Seriously, the code should be easy to understand without them.

    undefinedundefined

    There's a great rule about that: "if the code is so unclear that it requires a comment, then maybe it should be rewritten instead".

    undefinedundefined

    Recipe: factor out functions

    undefinedundefined

    Sometimes it's beneficial to replace a code piece with a function, like here:

    undefinedundefinedundefinedundefined

    The better variant, with a factored out function undefinedundefinedisPrime:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedshowPrimes(n) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfor (undefinedundefinedlet i undefinedundefined=undefinedundefined2undefinedundefined; i undefinedundefined< nundefinedundefined; iundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedif (undefinedundefined!undefinedundefinedisPrime(i)) undefinedundefinedcontinueundefinedundefined;*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined    alertundefinedundefined(undefinedundefinediundefinedundefined)undefinedundefined;  undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunction isPrimeundefinedundefined(undefinedundefinednundefinedundefined)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined  for undefinedundefined(undefinedundefinedlet i = 2; i < n; iundefinedundefined++)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined    if undefinedundefined(undefinedundefinedn % i == 0undefinedundefined)undefinedundefined return false;undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined  return true;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Now we can understand the code easily. The function itself becomes the comment. Such code is called undefinedundefinedself-descriptive.undefinedundefined

    undefinedundefined

    Recipe: create functions

    undefinedundefined

    And if we have a long "code sheet" like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// here we add whiskeyundefinedundefinedundefinedundefinedundefinedundefinedfor(undefinedundefinedlet i undefinedundefined=undefinedundefined0undefinedundefined; i undefinedundefined<undefinedundefined10undefinedundefined; iundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedlet drop undefinedundefined=undefinedundefinedgetWhiskey()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedsmell(drop)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedadd(dropundefinedundefined, glass)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// here we add juiceundefinedundefinedundefinedundefinedundefinedundefinedfor(undefinedundefinedlet t undefinedundefined=undefinedundefined0undefinedundefined; t undefinedundefined<undefinedundefined3undefinedundefined; tundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedlet tomato undefinedundefined=undefinedundefinedgetTomato()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedexamine(tomato)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedlet juice undefinedundefined=undefinedundefinedpress(tomato)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedadd(juiceundefinedundefined, glass)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Then it might be a better variant to refactor it into functions like:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedaddWhiskey(glass)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedaddJuice(glass)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedaddWhiskey(container) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedfor(undefinedundefinedlet i undefinedundefined=undefinedundefined0undefinedundefined; i undefinedundefined<undefinedundefined10undefinedundefined; iundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedlet drop undefinedundefined=undefinedundefinedgetWhiskey()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined//...undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedaddJuice(container) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedfor(undefinedundefinedlet t undefinedundefined=undefinedundefined0undefinedundefined; t undefinedundefined<undefinedundefined3undefinedundefined; tundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedlet tomato undefinedundefined=undefinedundefinedgetTomato()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined//...undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Once again, functions themselves tell what's going on. There's nothing to comment. And also the code structure is better when split. It's clear what every function does, what it takes and what it returns.

    undefinedundefined

    In reality, we can't totally avoid "explanatory" comments. There are complex algorithms. And there are smart "tweaks" for purposes of optimization. But generally we should try to keep the code simple and self-descriptive.

    undefinedundefined

    Good comments

    undefinedundefined

    So, explanatory comments are usually bad. Which comments are good?

    undefinedundefined
    undefinedundefined
    Describe the architecture
    undefinedundefined
    Provide a high-level overview of components, how they interact, what's the control flow in various situations… In short - the bird's eye view of the code. There's a special language undefinedundefinedUML to build high-level architecture diagrams explaining the code. Definitely worth studying. undefinedundefined
    undefinedundefined
    Document function parameters and usage
    undefinedundefined
    There's a special syntax undefinedundefinedJSDoc to document a function: usage, parameters, returned value. undefinedundefined
    undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    Such comments allow us to understand the purpose of the function and use it the right way without looking in its code.

    undefinedundefined

    By the way, many editors like undefinedundefinedWebStorm can understand them as well and use them to provide autocomplete and some automatic code-checking.undefinedundefined

    undefinedundefined

    Also, there are tools like undefinedundefinedJSDoc 3 that can generate HTML-documentation from the comments. You can read more information about JSDoc at undefinedundefinedhttp://usejsdoc.org/.undefinedundefined

    undefinedundefined
    undefinedundefined
    Why is the task solved this way?
    undefinedundefined
    undefinedundefined

    What's written is important. But what's undefinedundefinednot written may be even more important to understand what's going on. Why is the task solved exactly this way? The code gives no answer.undefinedundefined

    undefinedundefined

    If there are many ways to solve the task, why this one? Especially when it's not the most obvious one.

    undefinedundefined

    Without such comments the following situation is possible: 1. You (or your colleague) open the code written some time ago, and see that it's "suboptimal". 2. You think: "How stupid I was then, and how much smarter I'm now", and rewrite using the "more obvious and correct" variant. 3. …The urge to rewrite was good. But in the process you see that the "more obvious" solution is actually lacking. You even dimly remember why, because you already tried it long ago. You revert to the correct variant, but the time was wasted.

    undefinedundefined

    Comments that explain the solution are very important. They help to continue development the right way.

    undefinedundefined
    undefinedundefined
    Any subtle features of the code? Where they are used?
    undefinedundefined
    If the code has anything subtle and counter-intuitive, it's definitely worth commenting.
    undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    An important sign of a good developer is comments: their presence and even their absence.

    undefinedundefined

    Good comments allow us to maintain the code well, come back to it after a delay and use it more effectively.

    undefinedundefined

    undefinedundefinedComment this:undefinedundefined

    undefinedundefined
      undefinedundefined
    • Overall architecture, high-level view.
    • undefinedundefined
    • Function usage.
    • undefinedundefined
    • Important solutions, especially when not immediately obvious.
    • undefinedundefined
    undefinedundefined

    undefinedundefinedAvoid comments:undefinedundefined

    undefinedundefined
      undefinedundefined
    • That tell "how code works" and "what it does".
    • undefinedundefined
    • Put them in only if it's impossible to make the code so simple and self-descriptive that it doesn't require them.
    • undefinedundefined
    undefinedundefined

    Comments are also used for auto-documenting tools like JSDoc3: they read them and generate HTML-docs (or docs in another format).

    undefinedundefined

    Ninja code

    undefinedundefined

    undefinedundefinedquote author="Confucius (Analects)" Learning without thought is labor lost; thought without learning is perilous.undefinedundefined

    undefinedundefined

    Programmer ninjas of the past used these tricks to sharpen the mind of code maintainers.

    undefinedundefined

    Code review gurus look for them in test tasks.

    undefinedundefined

    Novice developers sometimes use them even better than programmer ninjas.

    undefinedundefined

    Read them carefully and find out who you are - a ninja, a novice, or maybe a code reviewer?

    undefinedundefined

    undefinedundefinedwarn header="Irony detected" Many try to follow ninja paths. Few succeed.undefinedundefined

    undefinedundefined

    Brevity is the soul of wit

    undefinedundefined

    Make the code as short as possible. Show how smart you are.

    undefinedundefined

    Let subtle language features guide you.

    undefinedundefined

    For instance, take a look at this ternary operator undefinedundefined'?':undefinedundefined

    undefinedundefinedundefinedundefined

    Cool, right? If you write like that, a developer who comes across this line and tries to understand what is the value of undefinedundefinedi is going to have a merry time. Then come to you, seeking for an answer.undefinedundefined

    undefinedundefined

    Tell them that shorter is always better. Initiate them into the paths of ninja.

    undefinedundefined

    One-letter variables

    undefinedundefined

    undefinedundefinedquote author="Laozi (Tao Te Ching)" The Dao hides in wordlessness. Only the Dao is well begun and well completed.undefinedundefined

    undefinedundefined

    Another way to code shorter is to use single-letter variable names everywhere. Like undefinedundefineda, undefinedundefinedb or undefinedundefinedc.undefinedundefined

    undefinedundefined

    A short variable disappears in the code like a real ninja in the forest. No one will be able to find it using "search" of the editor. And even if someone does, they won't be able to "decipher" what the name undefinedundefineda or undefinedundefinedb means.undefinedundefined

    undefinedundefined

    …But there's an exception. A real ninja will never use undefinedundefinedi as the counter in a undefinedundefined"for" loop. Anywhere, but not here. Look around, there are many more exotic letters. For instance, undefinedundefinedx or undefinedundefinedy.undefinedundefined

    undefinedundefined

    An exotic variable as a loop counter is especially cool if the loop body takes 1-2 pages (make it longer if you can). Then if someone looks deep inside the loop, they won't be able to quickly figure out that the variable named undefinedundefinedx is the loop counter.undefinedundefined

    undefinedundefined

    Use abbreviations

    undefinedundefined

    If the team rules forbid the use of one-letter and vague names - shorten them, make abbreviations.

    undefinedundefined

    Like this:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedlist -> undefinedundefinedlst.undefinedundefined
    • undefinedundefined
    • undefinedundefineduserAgent -> undefinedundefinedua.undefinedundefined
    • undefinedundefined
    • undefinedundefinedbrowser -> undefinedundefinedbrsr.undefinedundefined
    • undefinedundefined
    • …etc
    • undefinedundefined
    undefinedundefined

    Only the one with truly good intuition will be able to understand such names. Try to shorten everything. Only a worthy person should be able to uphold the development of your code.

    undefinedundefined

    Soar high. Be abstract.

    undefinedundefined

    undefinedundefinedquote author="Laozi (Tao Te Ching)" The great square is cornerless<br> The great vessel is last complete,<br> The great note is rarified sound,<br> The great image has no form.undefinedundefined

    undefinedundefined

    While choosing a name try to use the most abstract word. Like undefinedundefinedobj, undefinedundefineddata, undefinedundefinedvalue, undefinedundefineditem, undefinedundefinedelem and so on.undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      undefinedundefinedThe ideal name for a variable is undefinedundefineddata.undefinedundefined Use it everywhere you can. Indeed, every variable holds undefinedundefineddata, right?undefinedundefined

      undefinedundefined

      …But what to do if undefinedundefineddata is already taken? Try undefinedundefinedvalue, it's also universal. After all, a variable eventually gets a undefinedundefinedvalue.undefinedundefined

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedName a variable by its type: undefinedundefinedstr, undefinedundefinednum…undefinedundefinedundefinedundefined

      undefinedundefined

      Give them a try. A young initiate may wonder - are such names really useful for a ninja? Indeed, they are!

      undefinedundefined

      Sure, the variable name still means something. It says what's inside the variable: a string, a number or something else. But when an outsider tries to understand the code, they'll be surprised to see that there's actually no information at all! And will ultimately fail to alter your well-thought code.

      undefinedundefined

      The value type is easy to find out by debugging. But what's the meaning of the variable? Which string/number does it store?

      undefinedundefined

      There's just no way to figure out without a good meditation!

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefined…But what if there are no more such names? Just add a number: undefinedundefineddata1, item2, elem5…undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Attention test

    undefinedundefined

    Only a truly attentive programmer should be able to understand your code. But how to check that?

    undefinedundefined

    undefinedundefinedOne of the ways - use similar variable names, like undefinedundefineddate and undefinedundefineddata.undefinedundefinedundefinedundefined

    undefinedundefined

    Mix them where you can.

    undefinedundefined

    A quick read of such code becomes impossible. And when there's a typo… Ummm… We're stuck for long, time to drink tea.

    undefinedundefined

    Smart synonyms

    undefinedundefined

    undefinedundefinedquote author="Laozi (Tao Te Ching)" The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name.undefinedundefined

    undefinedundefined

    Using undefinedundefinedsimilar names for undefinedundefinedsame things makes life more interesting and shows your creativity to the public.undefinedundefined

    undefinedundefined

    For instance, consider function prefixes. If a function shows a message on the screen - start it with undefinedundefineddisplay…, like undefinedundefineddisplayMessage. And then if another function shows on the screen something else, like a user name, start it with undefinedundefinedshow… (like undefinedundefinedshowName).undefinedundefined

    undefinedundefined

    Insinuate that there's a subtle difference between such functions, while there is none.

    undefinedundefined

    Make a pact with fellow ninjas of the team: if John starts "showing" functions with undefinedundefineddisplay... in his code, then Peter could use undefinedundefinedrender.., and Ann - undefinedundefinedpaint.... Note how much more interesting and diverse the code became.undefinedundefined

    undefinedundefined

    …And now the hat trick!

    undefinedundefined

    For two functions with important differences - use the same prefix!

    undefinedundefined

    For instance, the function undefinedundefinedprintPage(page) will use a printer. And the function undefinedundefinedprintText(text) will put the text on-screen. Let an unfamiliar reader think well over similarly named function undefinedundefinedprintMessage: "Where does it put the message? To a printer or on the screen?". To make it really shine, undefinedundefinedprintMessage(message) should output it in the new window!undefinedundefined

    undefinedundefined

    Reuse names

    undefinedundefined

    undefinedundefinedquote author="Laozi (Tao Te Ching)" Once the whole is divided, the parts<br> need names.<br> There are already enough names.<br> One must know when to stop.undefinedundefined

    undefinedundefined

    Add a new variable only when absolutely necessary.

    undefinedundefined

    Instead, reuse existing names. Just write new values into them.

    undefinedundefined

    In a function try to use only variables passed as parameters.

    undefinedundefined

    That would make it really hard to identify what's exactly in the variable undefinedundefinednow. And also where it comes from. The purpose is to develop the intuition and memory of a person reading the code. A person with weak intuition would have to analyze the code line-by-line and track the changes through every code branch.undefinedundefined

    undefinedundefined

    undefinedundefinedAn advanced variant of the approach is to covertly (!) replace the value with something alike in the middle of a loop or a function.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    A fellow programmer who wants to work with undefinedundefinedelem in the second half of the function will be surprised… Only during the debugging, after examining the code they will find out that they're working with a clone!undefinedundefined

    undefinedundefined

    Seen in code regularly. Deadly effective even against an experienced ninja.

    undefinedundefined

    Underscores for fun

    undefinedundefined

    Put underscores undefinedundefined_ and undefinedundefined__ before variable names. Like undefinedundefined_name or undefinedundefined__value. It would be great if only you knew their meaning. Or, better, add them just for fun, without particular meaning at all. Or different meanings in different places.undefinedundefined

    undefinedundefined

    You kill two rabbits with one shot. First, the code becomes longer and less readable, and the second, a fellow developer may spend a long time trying to figure out what the underscores mean.

    undefinedundefined

    A smart ninja puts underscores at one spot of code and evades them at other places. That makes the code even more fragile and increases the probability of future errors.

    undefinedundefined

    Show your love

    undefinedundefined

    Let everyone see how magnificent your entities are! Names like undefinedundefinedsuperElement, undefinedundefinedmegaFrame and undefinedundefinedniceItem will definitely enlighten a reader.undefinedundefined

    undefinedundefined

    Indeed, from one hand, something is written: undefinedundefinedsuper.., undefinedundefinedmega.., undefinedundefinednice.. But from the other hand - that brings no details. A reader may decide to look for a hidden meaning and meditate for an hour or two of their paid working time.undefinedundefined

    undefinedundefined

    Overlap outer variables

    undefinedundefined

    undefinedundefinedquote author="Guan Yin Zi" When in the light, can't see anything in the darkness.<br> When in the darkness, can see everything in the light.undefinedundefined

    undefinedundefined

    Use same names for variables inside and outside a function. As simple. No efforts to invent new names.

    undefinedundefinedundefinedundefined

    A programmer who jumps inside the undefinedundefinedrender will probably fail to notice that there's a local undefinedundefineduser shadowing the outer one.undefinedundefined

    undefinedundefined

    Then they'll try to work with undefinedundefineduser assuming that it's the external variable, the result of undefinedundefinedauthenticateUser()… The trap is sprung! Hello, debugger…undefinedundefined

    undefinedundefined

    Side-effects everywhere!

    undefinedundefined

    There are functions that look like they don't change anything. Like undefinedundefinedisReady(), undefinedundefinedcheckPermission(), undefinedundefinedfindTags()… They are assumed to carry out calculations, find and return the data, without changing anything outside of them. In other words, without "side-effects".undefinedundefined

    undefinedundefined

    undefinedundefinedA really beautiful trick is to add a "useful" action to them, besides the main task.undefinedundefined

    undefinedundefined

    An expression of dazed surprise on the face of your colleague when they see a function named undefinedundefinedis.., undefinedundefinedcheck.. or undefinedundefinedfind... changing something - will definitely broaden your boundaries of reason.undefinedundefined

    undefinedundefined

    undefinedundefinedAnother way to surprise is to return a non-standard result.undefinedundefined

    undefinedundefined

    Show your original thinking! Let the call of undefinedundefinedcheckPermission return not undefinedundefinedtrue/false, but a complex object with the results of the check.undefinedundefined

    undefinedundefined

    Those developers who try to write undefinedundefinedif (checkPermission(..)), will wonder why it doesn't work. Tell them: "Read the docs!". And give this article.undefinedundefined

    undefinedundefined

    Powerful functions!

    undefinedundefined

    undefinedundefinedquote author="Laozi (Tao Te Ching)" The great Tao flows everywhere,<br> both to the left and to the right.undefinedundefined

    undefinedundefined

    Don't limit the function by what's written in its name. Be broader.

    undefinedundefined

    For instance, a function undefinedundefinedvalidateEmail(email) could (besides checking the email for correctness) show an error message and ask to re-enter the email.undefinedundefined

    undefinedundefined

    Additional actions should not be obvious from the function name. A true ninja coder will make them not obvious from the code as well.

    undefinedundefined

    undefinedundefinedJoining several actions into one protects your code from reuse.undefinedundefined

    undefinedundefined

    Imagine, another developer wants only to check the email, and not output any message. Your function undefinedundefinedvalidateEmail(email) that does both will not suit them. So they won't break your meditation by asking anything about it.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    All "pieces of advice" above are from the real code… Sometimes, written by experienced developers. Maybe even more experienced than you are ;)

    undefinedundefined
      undefinedundefined
    • Follow some of them, and your code will become full of surprises.
    • undefinedundefined
    • Follow many of them, and your code will become truly yours, no one would want to change it.
    • undefinedundefined
    • Follow all, and your code will become a valuable lesson for young developers looking for enlightenment.
    • undefinedundefined
    undefinedundefined

    Automated testing with Mocha

    undefinedundefined

    Automated testing will be used in further tasks, and it's also widely used in real projects.

    undefinedundefined

    Why do we need tests?

    undefinedundefined

    When we write a function, we can usually imagine what it should do: which parameters give which results.

    undefinedundefined

    During development, we can check the function by running it and comparing the outcome with the expected one. For instance, we can do it in the console.

    undefinedundefined

    If something is wrong - then we fix the code, run again, check the result - and so on till it works.

    undefinedundefined

    But such manual "re-runs" are imperfect.

    undefinedundefined

    undefinedundefinedWhen testing a code by manual re-runs, it's easy to miss something.undefinedundefined

    undefinedundefined

    For instance, we're creating a function undefinedundefinedf. Wrote some code, testing: undefinedundefinedf(1) works, but undefinedundefinedf(2) doesn't work. We fix the code and now undefinedundefinedf(2) works. Looks complete? But we forgot to re-test undefinedundefinedf(1). That may lead to an error.undefinedundefined

    undefinedundefined

    That's very typical. When we develop something, we keep a lot of possible use cases in mind. But it's hard to expect a programmer to check all of them manually after every change. So it becomes easy to fix one thing and break another one.

    undefinedundefined

    undefinedundefinedAutomated testing means that tests are written separately, in addition to the code. They run our functions in various ways and compare results with the expected.undefinedundefined

    undefinedundefined

    Behavior Driven Development (BDD)

    undefinedundefined

    Let's start with a technique named undefinedundefinedBehavior Driven Development or, in short, BDD.undefinedundefined

    undefinedundefined

    undefinedundefinedBDD is three things in one: tests AND documentation AND examples.undefinedundefined

    undefinedundefined

    To understand BDD, we'll examine a practical case of development.

    undefinedundefined

    Development of "pow": the spec

    undefinedundefined

    Let's say we want to make a function undefinedundefinedpow(x, n) that raises undefinedundefinedx to an integer power undefinedundefinedn. We assume that undefinedundefinedn≥0.undefinedundefined

    undefinedundefined

    That task is just an example: there's the undefinedundefined** operator in JavaScript that can do that, but here we concentrate on the development flow that can be applied to more complex tasks as well.undefinedundefined

    undefinedundefined

    Before creating the code of undefinedundefinedpow, we can imagine what the function should do and describe it.undefinedundefined

    undefinedundefined

    Such description is called a undefinedundefinedspecification or, in short, a spec, and contains descriptions of use cases together with tests for them, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    A spec has three main building blocks that you can see above:

    undefinedundefined
    undefinedundefined
    undefinedundefineddescribe("title", function() { ... })undefinedundefined
    undefinedundefined
    What functionality we're describing. In our case we're describing the function undefinedundefinedpow. Used to group "workers" - the undefinedundefinedit blocks. undefinedundefined
    undefinedundefined
    undefinedundefinedit("use case description", function() { ... })undefinedundefined
    undefinedundefined
    In the title of undefinedundefinedit we undefinedundefinedin a human-readable way describe the particular use case, and the second argument is a function that tests it. undefinedundefined
    undefinedundefined
    undefinedundefinedassert.equal(value1, value2)undefinedundefined
    undefinedundefined
    undefinedundefined

    The code inside undefinedundefinedit block, if the implementation is correct, should execute without errors.undefinedundefined

    undefinedundefined

    Functions undefinedundefinedassert.* are used to check whether undefinedundefinedpow works as expected. Right here we're using one of them - undefinedundefinedassert.equal, it compares arguments and yields an error if they are not equal. Here it checks that the result of undefinedundefinedpow(2, 3) equals undefinedundefined8. There are other types of comparisons and checks, that we'll add later.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    The specification can be executed, and it will run the test specified in undefinedundefinedit block. We'll see that later.undefinedundefined

    undefinedundefined

    The development flow

    undefinedundefined

    The flow of development usually looks like this:

    undefinedundefined
      undefinedundefined
    1. An initial spec is written, with tests for the most basic functionality.
    2. undefinedundefined
    3. An initial implementation is created.
    4. undefinedundefined
    5. To check whether it works, we run the testing framework undefinedundefinedMocha (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works.undefinedundefined
    6. undefinedundefined
    7. Now we have a working initial implementation with tests.
    8. undefinedundefined
    9. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail.
    10. undefinedundefined
    11. Go to 3, update the implementation till tests give no errors.
    12. undefinedundefined
    13. Repeat steps 3-6 till the functionality is ready.
    14. undefinedundefined
    undefinedundefined

    So, the development is undefinedundefinediterative. We write the spec, implement it, make sure tests pass, then write more tests, make sure they work etc. At the end we have both a working implementation and tests for it.undefinedundefined

    undefinedundefined

    Let's see this development flow in our practical case.

    undefinedundefined

    The first step is already complete: we have an initial spec for undefinedundefinedpow. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail).undefinedundefined

    undefinedundefined

    The spec in action

    undefinedundefined

    Here in the tutorial we'll be using the following JavaScript libraries for tests:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedMocha - the core framework: it provides common testing functions including undefinedundefineddescribe and undefinedundefinedit and the main function that runs tests.undefinedundefined
    • undefinedundefined
    • undefinedundefinedChai - the library with many assertions. It allows to use a lot of different assertions, for now we need only undefinedundefinedassert.equal.undefinedundefined
    • undefinedundefined
    • undefinedundefinedSinon - a library to spy over functions, emulate built-in functions and more, we'll need it much later.undefinedundefined
    • undefinedundefined
    undefinedundefined

    These libraries are suitable for both in-browser and server-side testing. Here we'll consider the browser variant.

    undefinedundefined

    The full HTML page with these frameworks and undefinedundefinedpow spec:undefinedundefined

    undefinedundefined

    undefinedundefinedhtml src="index.html"undefinedundefined

    undefinedundefined

    The page can be divided into five parts:

    undefinedundefined
      undefinedundefined
    1. The undefinedundefined<head> - add third-party libraries and styles for tests.undefinedundefined
    2. undefinedundefined
    3. The undefinedundefined<script> with the function to test, in our case - with the code for undefinedundefinedpow.undefinedundefined
    4. undefinedundefined
    5. The tests - in our case an external script undefinedundefinedtest.js that has undefinedundefineddescribe("pow", ...) from above.undefinedundefined
    6. undefinedundefined
    7. The HTML element undefinedundefined<div id="mocha"> will be used by Mocha to output results.undefinedundefined
    8. undefinedundefined
    9. The tests are started by the command undefinedundefinedmocha.run().undefinedundefined
    10. undefinedundefined
    undefinedundefined

    The result:

    undefinedundefined

    [iframe height=250 src="pow-1" border=1 edit]

    undefinedundefined

    As of now, the test fails, there's an error. That's logical: we have an empty function code in undefinedundefinedpow, so undefinedundefinedpow(2,3) returns undefinedundefinedundefined instead of undefinedundefined8.undefinedundefined

    undefinedundefined

    For the future, let's note that there are more high-level test-runners, like undefinedundefinedkarma and others, that make it easy to autorun many different tests.undefinedundefined

    undefinedundefined

    Initial implementation

    undefinedundefined

    Let's make a simple implementation of undefinedundefinedpow, for tests to pass:undefinedundefined

    undefinedundefinedundefinedundefined

    Wow, now it works!

    undefinedundefined

    [iframe height=250 src="pow-min" border=1 edit]

    undefinedundefined

    Improving the spec

    undefinedundefined

    What we've done is definitely a cheat. The function does not work: an attempt to calculate undefinedundefinedpow(3,4) would give an incorrect result, but tests pass.undefinedundefined

    undefinedundefined

    …But the situation is quite typical, it happens in practice. Tests pass, but the function works wrong. Our spec is imperfect. We need to add more use cases to it.

    undefinedundefined

    Let's add one more test to check that undefinedundefinedpow(3, 4) = 81.undefinedundefined

    undefinedundefined

    We can select one of two ways to organize the test here:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      The first variant - add one more undefinedundefinedassert into the same undefinedundefinedit:undefinedundefined

      undefinedundefinedundefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      The second - make two tests:

      undefinedundefinedundefinedundefined
    4. undefinedundefined
    undefinedundefined

    The principal difference is that when undefinedundefinedassert triggers an error, the undefinedundefinedit block immediately terminates. So, in the first variant if the first undefinedundefinedassert fails, then we'll never see the result of the second undefinedundefinedassert.undefinedundefined

    undefinedundefined

    Making tests separate is useful to get more information about what's going on, so the second variant is better.

    undefinedundefined

    And besides that, there's one more rule that's good to follow.

    undefinedundefined

    undefinedundefinedOne test checks one thing.undefinedundefined

    undefinedundefined

    If we look at the test and see two independent checks in it, it's better to split it into two simpler ones.

    undefinedundefined

    So let's continue with the second variant.

    undefinedundefined

    The result:

    undefinedundefined

    [iframe height=250 src="pow-2" edit border="1"]

    undefinedundefined

    As we could expect, the second test failed. Sure, our function always returns undefinedundefined8, while the undefinedundefinedassert expects undefinedundefined81.undefinedundefined

    undefinedundefined

    Improving the implementation

    undefinedundefined

    Let's write something more real for tests to pass:

    undefinedundefinedundefinedundefined

    To be sure that the function works well, let's test it for more values. Instead of writing undefinedundefinedit blocks manually, we can generate them in undefinedundefinedfor:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineddescribe(undefinedundefined"pow"undefinedundefined,undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedmakeTest(x) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedlet expected undefinedundefined= x undefinedundefined* x undefinedundefined* xundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedit(undefinedundefined`undefinedundefined${xundefinedundefined}undefinedundefined in the power 3 is undefinedundefined${expectedundefinedundefined}undefinedundefined`undefinedundefined,undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedassert.undefinedundefinedequal(undefinedundefinedpow(xundefinedundefined,undefinedundefined3)undefinedundefined, expected)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfor (undefinedundefinedlet x undefinedundefined=undefinedundefined1undefinedundefined; x undefinedundefined<=undefinedundefined5undefinedundefined; xundefinedundefined++) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedmakeTest(x)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The result:

    undefinedundefined

    [iframe height=250 src="pow-3" edit border="1"]

    undefinedundefined

    Nested describe

    undefinedundefined

    We're going to add even more tests. But before that let's note that the helper function undefinedundefinedmakeTest and undefinedundefinedfor should be grouped together. We won't need undefinedundefinedmakeTest in other tests, it's needed only in undefinedundefinedfor: their common task is to check how undefinedundefinedpow raises into the given power.undefinedundefined

    undefinedundefined

    Grouping is done with a nested undefinedundefineddescribe:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineddescribe(undefinedundefined"pow"undefinedundefined,undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefineddescribe(undefinedundefined"raises x to power 3"undefinedundefined,undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined    function makeTestundefinedundefined(undefinedundefinedxundefinedundefined)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined      let expected = x undefinedundefined*undefinedundefined x undefinedundefined*undefinedundefined x;undefinedundefinedundefinedundefinedundefinedundefined      itundefinedundefined(undefinedundefined`${x} in the power 3 is ${expected}`, functionundefinedundefined()undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined        assert.equalundefinedundefined(undefinedundefinedpowundefinedundefined(undefinedundefinedx, 3undefinedundefined)undefinedundefined, expectedundefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined      }undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined    }undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined    for undefinedundefined(undefinedundefinedlet x = 1; x <= 5; xundefinedundefined++)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined      makeTestundefinedundefined(undefinedundefinedxundefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined    }undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// ... more tests to follow here, both describe and it can be addedundefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The nested undefinedundefineddescribe defines a new "subgroup" of tests. In the output we can see the titled indentation:undefinedundefined

    undefinedundefined

    [iframe height=250 src="pow-4" edit border="1"]

    undefinedundefined

    In the future we can add more undefinedundefinedit and undefinedundefineddescribe on the top level with helper functions of their own, they won't see undefinedundefinedmakeTest.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="before/afterundefinedundefinedandbeforeEach/afterEachundefinedundefined" We can setupbefore/afterundefinedundefinedfunctions that execute before/after running tests, and alsobeforeEach/afterEachundefinedundefinedfunctions that execute before/after *every*it`.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    no-beautify describe("test", function() {

    undefinedundefined

    before(() => alert("Testing started - before all tests")); after(() => alert("Testing finished - after all tests"));

    undefinedundefined

    beforeEach(() => alert("Before a test - enter a test")); afterEach(() => alert("After a test - exit a test"));

    undefinedundefined

    it(‘test 1', () => alert(1)); it(‘test 2', () => alert(2));

    undefinedundefined

    });

    undefinedundefined
    undefinedundefined
    The running sequence will be:
    undefinedundefined
    undefinedundefined

    Testing started - before all tests (before) Before a test - enter a test (beforeEach) 1 After a test - exit a test (afterEach) Before a test - enter a test (beforeEach) 2 After a test - exit a test (afterEach) Testing finished - after all tests (after)

    undefinedundefined
    undefinedundefined
    [edit src="beforeafter" title="Open the example in the sandbox."]
    Usually, `beforeEach/afterEach` and `before/after` are used to perform initialization, zero out counters or do something else between the tests (or test groups).undefinedundefined
    undefinedundefined

    Extending the spec

    undefinedundefined

    The basic functionality of undefinedundefinedpow is complete. The first iteration of the development is done. When we're done celebrating and drinking champagne - let's go on and improve it.undefinedundefined

    undefinedundefined

    As it was said, the function undefinedundefinedpow(x, n) is meant to work with positive integer values undefinedundefinedn.undefinedundefined

    undefinedundefined

    To indicate a mathematical error, JavaScript functions usually return undefinedundefinedNaN. Let's do the same for invalid values of undefinedundefinedn.undefinedundefined

    undefinedundefined

    Let's first add the behavior to the spec(!):

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineddescribe(undefinedundefined"pow"undefinedundefined,undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedit(undefinedundefined"for negative n the result is NaN"undefinedundefined,undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefinedassert.undefinedundefinedisNaN(undefinedundefinedpow(undefinedundefined2undefinedundefined,undefinedundefined-1))undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined  itundefinedundefined(undefinedundefined"for non-integer n the result is NaN", functionundefinedundefined()undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined    assert.isNaNundefinedundefined(undefinedundefinedpowundefinedundefined(undefinedundefined2, 1.5undefinedundefined))undefinedundefined;    undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The result with new tests:

    undefinedundefined

    [iframe height=530 src="pow-nan" edit border="1"]

    undefinedundefined

    The newly added tests fail, because our implementation does not support them. That's how BDD is done: first we write failing tests, and then make an implementation for them.

    undefinedundefined

    ``undefinedundefinedsmart header="Other assertions" Please note the assertionassert.isNaNundefinedundefined: it checks forNaN`.undefinedundefined

    undefinedundefined

    There are other assertions in undefinedundefinedChai as well, for instance:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedassert.equal(value1, value2) - checks the equality undefinedundefinedvalue1 == value2.undefinedundefined
    • undefinedundefined
    • undefinedundefinedassert.strictEqual(value1, value2) - checks the strict equality undefinedundefinedvalue1 === value2.undefinedundefined
    • undefinedundefined
    • undefinedundefinedassert.notEqual, undefinedundefinedassert.notStrictEqual - inverse checks to the ones above.undefinedundefined
    • undefinedundefined
    • undefinedundefinedassert.isTrue(value) - checks that undefinedundefinedvalue === trueundefinedundefined
    • undefinedundefined
    • undefinedundefinedassert.isFalse(value) - checks that undefinedundefinedvalue === falseundefinedundefined
    • undefinedundefined
    • …the full list is in the undefinedundefineddocs undefinedundefined
    • undefinedundefined
    undefinedundefined

    So we should add a couple of lines to undefinedundefinedpow:undefinedundefined

    undefinedundefinedundefinedundefined

    Now it works, all tests pass:

    undefinedundefined

    [iframe height=300 src="pow-full" edit border="1"]

    undefinedundefined

    [edit src="pow-full" title="Open the full final example in the sandbox."]

    undefinedundefined

    Summary

    undefinedundefined

    In BDD, the spec goes first, followed by implementation. At the end we have both the spec and the code.

    undefinedundefined

    The spec can be used in three ways:

    undefinedundefined
      undefinedundefined
    1. As undefinedundefinedTests - they guarantee that the code works correctly.undefinedundefined
    2. undefinedundefined
    3. As undefinedundefinedDocs - the titles of undefinedundefineddescribe and undefinedundefinedit tell what the function does.undefinedundefined
    4. undefinedundefined
    5. As undefinedundefinedExamples - the tests are actually working examples showing how a function can be used.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    With the spec, we can safely improve, change, even rewrite the function from scratch and make sure it still works right.

    undefinedundefined

    That's especially important in large projects when a function is used in many places. When we change such a function, there's just no way to manually check if every place that uses it still works right.

    undefinedundefined

    Without tests, people have two ways:

    undefinedundefined
      undefinedundefined
    1. To perform the change, no matter what. And then our users meet bugs, as we probably fail to check something manually.
    2. undefinedundefined
    3. Or, if the punishment for errors is harsh, as there are no tests, people become afraid to modify such functions, and then the code becomes outdated, no one wants to get into it. Not good for development.
    4. undefinedundefined
    undefinedundefined

    undefinedundefinedAutomatic testing helps to avoid these problems!undefinedundefined

    undefinedundefined

    If the project is covered with tests, there's just no such problem. After any changes, we can run tests and see a lot of checks made in a matter of seconds.

    undefinedundefined

    undefinedundefinedBesides, a well-tested code has better architecture.undefinedundefined

    undefinedundefined

    Naturally, that's because auto-tested code is easier to modify and improve. But there's also another reason.

    undefinedundefined

    To write tests, the code should be organized in such a way that every function has a clearly described task, well-defined input and output. That means a good architecture from the beginning.

    undefinedundefined

    In real life that's sometimes not that easy. Sometimes it's difficult to write a spec before the actual code, because it's not yet clear how it should behave. But in general writing tests makes development faster and more stable.

    undefinedundefined

    Later in the tutorial you will meet many tasks with tests baked-in. So you'll see more practical examples.

    undefinedundefined

    Writing tests requires good JavaScript knowledge. But we're just starting to learn it. So, to settle down everything, as of now you're not required to write tests, but you should already be able to read them even if they are a little bit more complex than in this chapter.

    undefinedundefined

    Polyfills and transpilers

    undefinedundefined

    The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at undefinedundefinedhttps://tc39.github.io/ecma262/ and then progress to the undefinedundefinedspecification.undefinedundefined

    undefinedundefined

    Teams behind JavaScript engines have their own ideas about what to implement first. They may decide to implement proposals that are in draft and postpone things that are already in the spec, because they are less interesting or just harder to do.

    undefinedundefined

    So it's quite common for an engine to implement only the part of the standard.

    undefinedundefined

    A good page to see the current state of support for language features is undefinedundefinedhttps://kangax.github.io/compat-table/es6/ (it's big, we have a lot to study yet).undefinedundefined

    undefinedundefined

    As programmers, we'd like to use most recent features. The more good stuff - the better!

    undefinedundefined

    On the other hand, how to make our modern code work on older engines that don't understand recent features yet?

    undefinedundefined

    There are two tools for that:

    undefinedundefined
      undefinedundefined
    1. Transpilers.
    2. undefinedundefined
    3. Polyfills.
    4. undefinedundefined
    undefinedundefined

    Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development.

    undefinedundefined

    Transpilers

    undefinedundefined

    A undefinedundefinedtranspiler is a special piece of software that can parse ("read and understand") modern code, and rewrite it using older syntax constructs, so that the result would be the same.undefinedundefined

    undefinedundefined

    E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" undefinedundefined??. So, if a visitor uses an outdated browser, it may fail to understand the code like undefinedundefinedheight = height ?? 100.undefinedundefined

    undefinedundefined

    A transpiler would analyze our code and rewrite undefinedundefinedheight ?? 100 into undefinedundefined(height !== undefined && height !== null) ? height : 100.undefinedundefined

    undefinedundefinedundefinedundefined

    Now the rewritten code is suitable for older JavaScript engines.

    undefinedundefined

    Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server.

    undefinedundefined

    Speaking of names, undefinedundefinedBabel is one of the most prominent transpilers out there.undefinedundefined

    undefinedundefined

    Modern project build systems, such as undefinedundefinedwebpack, provide means to run transpiler automatically on every code change, so it's very easy to integrate into development process.undefinedundefined

    undefinedundefined

    Polyfills

    undefinedundefined

    New language features may include not only syntax constructs and operators, but also built-in functions.

    undefinedundefined

    For example, undefinedundefinedMath.trunc(n) is a function that "cuts off" the decimal part of a number, e.g undefinedundefinedMath.trunc(1.23) returns undefinedundefined1.undefinedundefined

    undefinedundefined

    In some (very outdated) JavaScript engines, there's no undefinedundefinedMath.trunc, so such code will fail.undefinedundefined

    undefinedundefined

    As we're talking about new functions, not syntax changes, there's no need to transpile anything here. We just need to declare the missing function.

    undefinedundefined

    A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations.

    undefinedundefined

    For this particular case, the polyfill for undefinedundefinedMath.trunc is a script that implements it, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    JavaScript is a highly dynamic language, scripts may add/modify any functions, even including built-in ones.

    undefinedundefined

    Two interesting libraries of polyfills are: - undefinedundefinedcore js that supports a lot, allows to include only needed features. - undefinedundefinedpolyfill.io service that provides a script with polyfills, depending on the features and user's browser.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines.

    undefinedundefined

    Just don't forget to use transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). And they'll ensure that the code works.

    undefinedundefined

    For example, later when you're familiar with JavaScript, you can setup a code build system based on undefinedundefinedwebpack with undefinedundefinedbabel-loader plugin.undefinedundefined

    undefinedundefined

    Good resources that show the current state of support for various features: - undefinedundefinedhttps://kangax.github.io/compat-table/es6/ - for pure JavaScript. - undefinedundefinedhttps://caniuse.com/ - for browser-related functions.undefinedundefined

    undefinedundefined

    P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though.

    undefinedundefined

    Objects

    undefinedundefined

    As we know from the chapter undefinedundefinedinfo:types, there are eight data types in JavaScript. Seven of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever).undefinedundefined

    undefinedundefined

    In contrast, objects are used to store keyed collections of various data and more complex entities. In JavaScript, objects penetrate almost every aspect of the language. So we must understand them first before going in-depth anywhere else.

    undefinedundefined

    An object can be created with figure brackets undefinedundefined{…} with an optional list of undefinedundefinedproperties. A property is a "key: value" pair, where undefinedundefinedkey is a string (also called a "property name"), and undefinedundefinedvalue can be anything.undefinedundefined

    undefinedundefined

    We can imagine an object as a cabinet with signed files. Every piece of data is stored in its file by the key. It's easy to find a file by its name or add/remove a file.

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    An empty object ("empty cabinet") can be created using one of two syntaxes:

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Usually, the figure brackets undefinedundefined{...} are used. That declaration is called an undefinedundefinedobject literal.undefinedundefined

    undefinedundefined

    Literals and properties

    undefinedundefined

    We can immediately put some properties into undefinedundefined{...} as "key: value" pairs:undefinedundefined

    undefinedundefinedundefinedundefined

    A property has a key (also known as "name" or "identifier") before the colon undefinedundefined":" and a value to the right of it.undefinedundefined

    undefinedundefined

    In the undefinedundefineduser object, there are two properties:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. The first property has the name undefinedundefined"name" and the value undefinedundefined"John".undefinedundefined
    2. undefinedundefined
    3. The second one has the name undefinedundefined"age" and the value undefinedundefined30.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    The resulting undefinedundefineduser object can be imagined as a cabinet with two signed files labeled "name" and "age".undefinedundefined

    undefinedundefined
    undefinedundefineduser objectundefinedundefined
    user object
    undefinedundefined
    undefinedundefined

    We can add, remove and read files from it any time.

    undefinedundefined

    Property values are accessible using the dot notation:

    undefinedundefinedundefinedundefined

    The value can be of any type. Let's add a boolean one:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineduser.undefinedundefinedisAdminundefinedundefined=undefinedundefinedtrueundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefineduser object 2undefinedundefined
    user object 2
    undefinedundefined
    undefinedundefined

    To remove a property, we can use undefinedundefineddelete operator:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineddeleteundefinedundefineduser.undefinedundefinedageundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefineduser object 3undefinedundefined
    user object 3
    undefinedundefined
    undefinedundefined

    We can also use multiword property names, but then they must be quoted:

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The last property in the list may end with a comma:

    undefinedundefinedundefinedundefined

    That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike.

    undefinedundefined

    Square brackets

    undefinedundefined

    For multiword properties, the dot access doesn't work:

    undefinedundefined

    undefinedundefinedjs run // this would give a syntax error user.likes birds = trueundefinedundefined

    undefinedundefined

    JavaScript doesn't understand that. It thinks that we address undefinedundefineduser.likes, and then gives a syntax error when comes across unexpected undefinedundefinedbirds.undefinedundefined

    undefinedundefined

    The dot requires the key to be a valid variable identifier. That implies: contains no spaces, doesn't start with a digit and doesn't include special characters (undefinedundefined$ and undefinedundefined_ are allowed).undefinedundefined

    undefinedundefined

    There's an alternative "square bracket notation" that works with any string:

    undefinedundefined

    run let user = {};

    undefinedundefined

    // set user["likes birds"] = true;

    undefinedundefined

    // get alert(user["likes birds"]); // true

    undefinedundefined

    // delete delete user["likes birds"];

    undefinedundefined

    Now everything is fine. Please note that the string inside the brackets is properly quoted (any type of quotes will do).

    undefinedundefined

    Square brackets also provide a way to obtain the property name as the result of any expression - as opposed to a literal string - like from a variable as follows:

    undefinedundefinedundefinedundefined

    Here, the variable undefinedundefinedkey may be calculated at run-time or depend on the user input. And then we use it to access the property. That gives us a great deal of flexibility.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    let key = prompt("What do you want to know about the user?", "name");

    undefinedundefined

    // access by variable alert( user[key] ); // John (if enter "name")

    undefinedundefined

    The dot notation cannot be used in a similar way:

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    let key = "name"; alert( user.key ) // undefined

    undefinedundefined

    Computed properties

    undefinedundefined

    We can use square brackets in an object literal, when creating an object. That's called undefinedundefinedcomputed properties.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let fruit = prompt("Which fruit to buy?", "apple");

    undefinedundefined

    let bag = { undefinedundefined! [fruit]: 5, // the name of the property is taken from the variable fruit undefinedundefined/! };undefinedundefined

    undefinedundefined

    alert( bag.apple ); // 5 if fruit="apple"

    undefinedundefined

    The meaning of a computed property is simple: undefinedundefined[fruit] means that the property name should be taken from undefinedundefinedfruit.undefinedundefined

    undefinedundefined

    So, if a visitor enters undefinedundefined"apple", undefinedundefinedbag will become undefinedundefined{apple: 5}.undefinedundefined

    undefinedundefined

    Essentially, that works the same as: run let fruit = prompt("Which fruit to buy?", "apple"); let bag = {};

    undefinedundefined

    // take property name from the fruit variable bag[fruit] = 5;

    undefinedundefined

    …But looks nicer.

    undefinedundefined

    We can use more complex expressions inside square brackets:

    undefinedundefinedundefinedundefined

    Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write.

    undefinedundefined

    So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.

    undefinedundefined

    Property value shorthand

    undefinedundefined

    In real code we often use existing variables as values for property names.

    undefinedundefined

    For instance:

    undefinedundefined

    run function makeUser(name, age) { return { name: name, age: age, // …other properties }; }

    undefinedundefined

    let user = makeUser("John", 30); alert(user.name); // John

    undefinedundefined

    In the example above, properties have the same names as variables. The use-case of making a property from a variable is so common, that there's a special undefinedundefinedproperty value shorthand to make it shorter.undefinedundefined

    undefinedundefined

    Instead of undefinedundefinedname:name we can just write undefinedundefinedname, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    We can use both normal properties and shorthands in the same object:

    undefinedundefinedundefinedundefined

    Property names limitations

    undefinedundefined

    As we already know, a variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.

    undefinedundefined

    But for an object property, there's no such restriction:

    undefinedundefined

    run // these properties are all right let obj = { for: 1, let: 2, return: 3 };

    undefinedundefined

    alert( obj.for + obj.let + obj.return ); // 6

    undefinedundefined

    In short, there are no limitations on property names. They can be any strings or symbols (a special type for identifiers, to be covered later).

    undefinedundefined

    Other types are automatically converted to strings.

    undefinedundefined

    For instance, a number undefinedundefined0 becomes a string undefinedundefined"0" when used as a property key:undefinedundefined

    undefinedundefined

    run let obj = { 0: "test" // same as "0": "test" };

    undefinedundefined

    // both alerts access the same property (the number 0 is converted to string "0") alert( obj["0"] ); // test alert( obj[0] ); // test (same property)

    undefinedundefined

    There's a minor gotcha with a special property named undefinedundefined__proto__. We can't set it to a non-object value:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let obj = {}; obj.__proto__ = 5; // assign a number alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intendedundefinedundefined

    undefinedundefined

    As we see from the code, the assignment to a primitive undefinedundefined5 is ignored.undefinedundefined

    undefinedundefined

    We'll cover the special nature of undefinedundefined__proto__ in undefinedundefinedsubsequent chapters, and suggest the undefinedundefinedways to fix such behavior.undefinedundefined

    undefinedundefined

    Property existence test, "in" operator

    undefinedundefined

    A notable feature of objects in JavaScript, compared to many other languages, is that it's possible to access any property. There will be no error if the property doesn't exist!

    undefinedundefined

    Reading a non-existing property just returns undefinedundefinedundefined. So we can easily test whether the property exists:undefinedundefined

    undefinedundefined

    run let user = {};

    undefinedundefined

    alert( user.noSuchProperty === undefined ); // true means "no such property"

    undefinedundefined

    There's also a special operator undefinedundefined"in" for that.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined"key"undefinedundefinedin objectundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    alert( "age" in user ); // true, user.age exists alert( "blabla" in user ); // false, user.blabla doesn't exist

    undefinedundefined

    Please note that on the left side of undefinedundefinedin there must be a undefinedundefinedproperty name. That's usually a quoted string.undefinedundefined

    undefinedundefined

    If we omit quotes, that means a variable, it should contain the actual name to be tested. For instance:

    undefinedundefined

    run let user = { age: 30 };

    undefinedundefined

    let key = "age"; alert( undefinedundefined!keyundefinedundefined/! in user ); // true, property "age" exists undefinedundefined

    undefinedundefined

    Why does the undefinedundefinedin operator exist? Isn't it enough to compare against undefinedundefinedundefined?undefinedundefined

    undefinedundefined

    Well, most of the time the comparison with undefinedundefinedundefined works fine. But there's a special case when it fails, but undefinedundefined"in" works correctly.undefinedundefined

    undefinedundefined

    It's when an object property exists, but stores undefinedundefinedundefined:undefinedundefined

    undefinedundefined

    run let obj = { test: undefined };

    undefinedundefined

    alert( obj.test ); // it's undefined, so - no such property?

    undefinedundefined

    alert( "test" in obj ); // true, the property does exist!

    undefinedundefined

    In the code above, the property undefinedundefinedobj.test technically exists. So the undefinedundefinedin operator works right.undefinedundefined

    undefinedundefined

    Situations like this happen very rarely, because undefinedundefinedundefined should not be explicitly assigned. We mostly use undefinedundefinednull for "unknown" or "empty" values. So the undefinedundefinedin operator is an exotic guest in the code.undefinedundefined

    undefinedundefined

    The "for..in" loop

    undefinedundefined

    To walk over all keys of an object, there exists a special form of the loop: undefinedundefinedfor..in. This is a completely different thing from the undefinedundefinedfor(;;) construct that we studied before.undefinedundefined

    undefinedundefined

    The syntax:

    undefinedundefinedundefinedundefined

    For instance, let's output all properties of undefinedundefineduser:undefinedundefined

    undefinedundefined

    run let user = { name: "John", age: 30, isAdmin: true };

    undefinedundefined

    for (let key in user) { // keys alert( key ); // name, age, isAdmin // values for the keys alert( user[key] ); // John, 30, true }

    undefinedundefined

    Note that all "for" constructs allow us to declare the looping variable inside the loop, like undefinedundefinedlet key here.undefinedundefined

    undefinedundefined

    Also, we could use another variable name here instead of undefinedundefinedkey. For instance, undefinedundefined"for (let prop in obj)" is also widely used.undefinedundefined

    undefinedundefined

    Ordered like an object

    undefinedundefined

    Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order they were added? Can we rely on this?

    undefinedundefined

    The short answer is: "ordered in a special fashion": integer properties are sorted, others appear in creation order. The details follow.

    undefinedundefined

    As an example, let's consider an object with the phone codes:

    undefinedundefined

    run let codes = { "49": "Germany", "41": "Switzerland", "44": "Great Britain", // .., "1": "USA" };

    undefinedundefined

    undefinedundefined! for (let code in codes) { alert(code); // 1, 41, 44, 49 } undefinedundefined/! undefinedundefined

    undefinedundefined

    The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want undefinedundefined49 to be the first.undefinedundefined

    undefinedundefined

    But if we run the code, we see a totally different picture:

    undefinedundefined
      undefinedundefined
    • USA (1) goes first
    • undefinedundefined
    • then Switzerland (41) and so on.
    • undefinedundefined
    undefinedundefined

    The phone codes go in the ascending sorted order, because they are integers. So we see undefinedundefined1, 41, 44, 49.undefinedundefined

    undefinedundefined

    smart header="Integer properties? What's that?" The "integer property" term here means a string that can be converted to-and-from an integer without a change.

    undefinedundefined

    So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:

    undefinedundefined

    undefinedundefinedjs run // Math.trunc is a built-in function that removes the decimal part alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property undefinedundefined

    undefinedundefined

    …On the other hand, if the keys are non-integer, then they are listed in the creation order, for instance:

    undefinedundefined

    run let user = { name: "John", surname: "Smith" }; user.age = 25; // add one more

    undefinedundefined

    undefinedundefined! // non-integer properties are listed in the creation order undefinedundefined/! for (let prop in user) { alert( prop ); // name, surname, age } undefinedundefined

    undefinedundefined

    So, to fix the issue with the phone codes, we can "cheat" by making the codes non-integer. Adding a plus undefinedundefined"+" sign before each code is enough.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run let codes = { "+49": "Germany", "+41": "Switzerland", "+44": "Great Britain", // .., "+1": "USA" };

    undefinedundefined

    for (let code in codes) { alert( +code ); // 49, 41, 44, 1 }

    undefinedundefined

    Now it works as intended.

    undefinedundefined

    Summary

    undefinedundefined

    Objects are associative arrays with several special features.

    undefinedundefined

    They store properties (key-value pairs), where: - Property keys must be strings or symbols (usually strings). - Values can be of any type.

    undefinedundefined

    To access a property, we can use: - The dot notation: undefinedundefinedobj.property. - Square brackets notation undefinedundefinedobj["property"]. Square brackets allow to take the key from a variable, like undefinedundefinedobj[varWithKey].undefinedundefined

    undefinedundefined

    Additional operators: - To delete a property: undefinedundefineddelete obj.prop. - To check if a property with the given key exists: undefinedundefined"key" in obj. - To iterate over an object: undefinedundefinedfor (let key in obj) loop.undefinedundefined

    undefinedundefined

    What we've studied in this chapter is called a "plain object", or just undefinedundefinedObject.undefinedundefined

    undefinedundefined

    There are many other kinds of objects in JavaScript:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedArray to store ordered data collections,undefinedundefined
    • undefinedundefined
    • undefinedundefinedDate to store the information about the date and time,undefinedundefined
    • undefinedundefined
    • undefinedundefinedError to store the information about an error.undefinedundefined
    • undefinedundefined
    • …And so on.
    • undefinedundefined
    undefinedundefined

    They have their special features that we'll study later. Sometimes people say something like "Array type" or "Date type", but formally they are not types of their own, but belong to a single "object" data type. And they extend it in various ways.

    undefinedundefined

    Objects in JavaScript are very powerful. Here we've just scratched the surface of a topic that is really huge. We'll be closely working with objects and learning more about them in further parts of the tutorial.

    undefinedundefined

    Code editors

    undefinedundefined

    A code editor is the place where programmers spend most of their time.

    undefinedundefined

    There are two main types of code editors: IDEs and lightweight editors. Many people use one tool of each type.

    undefinedundefined

    IDE

    undefinedundefined

    The term undefinedundefinedIDE (Integrated Development Environment) refers to a powerful editor with many features that usually operates on a "whole project." As the name suggests, it's not just an editor, but a full-scale "development environment."undefinedundefined

    undefinedundefined

    An IDE loads the project (which can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), and integrates with a version management system (like undefinedundefinedgit), a testing environment, and other "project-level" stuff.undefinedundefined

    undefinedundefined

    If you haven't selected an IDE yet, consider the following options:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedVisual Studio Code (cross-platform, free).undefinedundefined
    • undefinedundefined
    • undefinedundefinedWebStorm (cross-platform, paid).undefinedundefined
    • undefinedundefined
    undefinedundefined

    For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code". "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. It's also good at JavaScript. There's also a free version undefinedundefinedVisual Studio Community.undefinedundefined

    undefinedundefined

    Many IDEs are paid, but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you.

    undefinedundefined

    Lightweight editors

    undefinedundefined

    "Lightweight editors" are not as powerful as IDEs, but they're fast, elegant and simple.

    undefinedundefined

    They are mainly used to open and edit a file instantly.

    undefinedundefined

    The main difference between a "lightweight editor" and an "IDE" is that an IDE works on a project-level, so it loads much more data on start, analyzes the project structure if needed and so on. A lightweight editor is much faster if we need only one file.

    undefinedundefined

    In practice, lightweight editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE.

    undefinedundefined

    The following options deserve your attention:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedAtom (cross-platform, free).undefinedundefined
    • undefinedundefined
    • undefinedundefinedVisual Studio Code (cross-platform, free).undefinedundefined
    • undefinedundefined
    • undefinedundefinedSublime Text (cross-platform, shareware).undefinedundefined
    • undefinedundefined
    • undefinedundefinedNotepad++ (Windows, free).undefinedundefined
    • undefinedundefined
    • undefinedundefinedVim and undefinedundefinedEmacs are also cool if you know how to use them.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Let's not argue

    undefinedundefined

    The editors in the lists above are those that either I or my friends whom I consider good developers have been using for a long time and are happy with.

    undefinedundefined

    There are other great editors in our big world. Please choose the one you like the most.

    undefinedundefined

    The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences.

    undefinedundefined

    Object references and copying

    undefinedundefined

    One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", whereas primitive values: strings, numbers, booleans, etc - are always copied "as a whole value".

    undefinedundefined

    That's easy to understand if we look a bit under the hood of what happens when we copy a value.

    undefinedundefined

    Let's start with a primitive, such as a string.

    undefinedundefined

    Here we put a copy of undefinedundefinedmessage into undefinedundefinedphrase:undefinedundefined

    undefinedundefinedundefinedundefined

    As a result we have two independent variables, each one storing the string undefinedundefined"Hello!".undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Quite an obvious result, right?

    undefinedundefined

    Objects are not like that.

    undefinedundefined

    undefinedundefinedA variable assigned to an object stores not the object itself, but its "address in memory" - in other words "a reference" to it.undefinedundefined

    undefinedundefined

    Let's look at an example of such a variable:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet user undefinedundefined=undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinednameundefinedundefined:undefinedundefined"John"undefinedundefinedundefinedundefinedundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    And here's how it's actually stored in memory:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The object is stored somewhere in memory (at the right of the picture), while the undefinedundefineduser variable (at the left) has a "reference" to it.undefinedundefined

    undefinedundefined

    We may think of an object variable, such as undefinedundefineduser, as like a sheet of paper with the address of the object on it.undefinedundefined

    undefinedundefined

    When we perform actions with the object, e.g. take a property undefinedundefineduser.name, the JavaScript engine looks at what's at that address and performs the operation on the actual object.undefinedundefined

    undefinedundefined

    Now here's why it's important.

    undefinedundefined

    undefinedundefinedWhen an object variable is copied, the reference is copied, but the object itself is not duplicated.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    no-beautify let user = { name: "John" };

    undefinedundefined

    let admin = user; // copy the reference

    undefinedundefined

    Now we have two variables, each storing a reference to the same object:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    As you can see, there's still one object, but now with two variables that reference it.

    undefinedundefined

    We can use either variable to access the object and modify its contents:

    undefinedundefined

    run let user = { name: ‘John' };

    undefinedundefined

    let admin = user;

    undefinedundefined

    undefinedundefined! admin.name = ‘Pete'; // changed by the "admin" reference undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(undefinedundefined!user.nameundefinedundefined/!); // ‘Pete', changes are seen from the "user" reference undefinedundefined

    undefinedundefined

    It's as if we had a cabinet with two keys and used one of them (undefinedundefinedadmin) to get into it and make changes. Then, if we later use another key (undefinedundefineduser), we are still opening the same cabinet and can access the changed contents.undefinedundefined

    undefinedundefined

    Comparison by reference

    undefinedundefined

    Two objects are equal only if they are the same object.

    undefinedundefined

    For instance, here undefinedundefineda and undefinedundefinedb reference the same object, thus they are equal:undefinedundefined

    undefinedundefined

    run let a = {}; let b = a; // copy the reference

    undefinedundefined

    alert( a == b ); // true, both variables reference the same object alert( a === b ); // true

    undefinedundefined

    And here two independent objects are not equal, even though they look alike (both are empty):

    undefinedundefined

    run let a = {}; let b = {}; // two independent objects

    undefinedundefined

    alert( a == b ); // false

    undefinedundefined

    For comparisons like undefinedundefinedobj1 > obj2 or for a comparison against a primitive undefinedundefinedobj == 5, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely - usually they appear as a result of a programming mistake.undefinedundefined

    undefinedundefined

    Cloning and merging, Object.assign [#cloning-and-merging-object-assign]

    undefinedundefined

    So, copying an object variable creates one more reference to the same object.

    undefinedundefined

    But what if we need to duplicate an object? Create an independent copy, a clone?

    undefinedundefined

    That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. But there is rarely a need - copying by reference is good most of the time.

    undefinedundefined

    But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.

    undefinedundefined

    Like this:

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    undefinedundefined! let clone = {}; // the new empty objectundefinedundefined

    undefinedundefined

    // let's copy all user properties into it for (let key in user) { clone[key] = user[key]; } undefinedundefined/!undefinedundefined

    undefinedundefined

    // now clone is a fully independent object with the same content clone.name = "Pete"; // changed the data in it

    undefinedundefined

    alert( user.name ); // still John in the original object

    undefinedundefined

    Also we can use the method undefinedundefinedObject.assign for that.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedObject.undefinedundefinedassign(destundefinedundefined, [src1undefinedundefined, src2undefinedundefined,undefinedundefinedsrc3...])undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
      undefinedundefined
    • The first argument undefinedundefineddest is a target object.undefinedundefined
    • undefinedundefined
    • Further arguments undefinedundefinedsrc1, ..., srcN (can be as many as needed) are source objects.undefinedundefined
    • undefinedundefined
    • It copies the properties of all source objects undefinedundefinedsrc1, ..., srcN into the target undefinedundefineddest. In other words, properties of all arguments starting from the second are copied into the first object.undefinedundefined
    • undefinedundefined
    • The call returns undefinedundefineddest.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance, we can use it to merge several objects into one:

    undefinedundefined undefinedundefined

    If the copied property name already exists, it gets overwritten:

    undefinedundefined

    run let user = { name: "John" };

    undefinedundefined

    Object.assign(user, { name: "Pete" });

    undefinedundefined

    alert(user.name); // now user = { name: "Pete" }

    undefinedundefined

    We also can use undefinedundefinedObject.assign to replace undefinedundefinedfor..in loop for simple cloning:undefinedundefined

    undefinedundefinedundefinedundefined

    It copies all properties of undefinedundefineduser into the empty object and returns it.undefinedundefined

    undefinedundefined

    There are also other methods of cloning an object, e.g. using the undefinedundefinedspread syntaxundefinedundefinedclone = {...user}, covered later in the tutorial.undefinedundefined

    undefinedundefined

    Nested cloning

    undefinedundefined

    Until now we assumed that all properties of undefinedundefineduser are primitive. But properties can be references to other objects. What to do with them?undefinedundefined

    undefinedundefined

    Like this: run let user = { name: "John", sizes: { height: 182, width: 50 } };

    undefinedundefined

    alert( user.sizes.height ); // 182

    undefinedundefined

    Now it's not enough to copy undefinedundefinedclone.sizes = user.sizes, because the undefinedundefineduser.sizes is an object, it will be copied by reference. So undefinedundefinedclone and undefinedundefineduser will share the same sizes:undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run let user = { name: "John", sizes: { height: 182, width: 50 } };

    undefinedundefined

    let clone = Object.assign({}, user);

    undefinedundefined

    alert( user.sizes === clone.sizes ); // true, same object

    undefinedundefined

    // user and clone share sizes user.sizes.width++; // change a property from one place alert(clone.sizes.width); // 51, see the result from the other one

    undefinedundefined

    To fix that, we should use a cloning loop that examines each value of undefinedundefineduser[key] and, if it's an object, then replicate its structure as well. That is called a "deep cloning".undefinedundefined

    undefinedundefined

    We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance undefinedundefined_.cloneDeep(obj) from the JavaScript library undefinedundefinedlodash.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Const objects can be modified" An important side effect of storing objects as references is that an object declared asconst` undefinedundefinedcan be modified.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run const user = { name: "John" };

    undefinedundefined

    undefinedundefined! user.name = "Pete"; // (undefinedundefined) /!*undefinedundefined

    undefinedundefined

    alert(user.name); // Pete

    undefinedundefined
    undefinedundefined
    It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change.
    In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
    That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter <info:property-descriptors>.undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself.

    undefinedundefined

    All operations via copied references (like adding/removing properties) are performed on the same single object.

    undefinedundefined

    To make a "real copy" (a clone) we can use undefinedundefinedObject.assign for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as undefinedundefined_.cloneDeep(obj).undefinedundefined

    undefinedundefined

    Garbage collection

    undefinedundefined

    Memory management in JavaScript is performed automatically and invisibly to us. We create primitives, objects, functions… All that takes memory.

    undefinedundefined

    What happens when something is not needed any more? How does the JavaScript engine discover it and clean it up?

    undefinedundefined

    Reachability

    undefinedundefined

    The main concept of memory management in JavaScript is undefinedundefinedreachability.undefinedundefined

    undefinedundefined

    Simply put, "reachable" values are those that are accessible or usable somehow. They are guaranteed to be stored in memory.

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      There's a base set of inherently reachable values, that cannot be deleted for obvious reasons.

      undefinedundefined

      For instance:

      undefinedundefined
        undefinedundefined
      • The currently executing function, its local variables and parameters.
      • undefinedundefined
      • Other functions on the current chain of nested calls, their local variables and parameters.
      • undefinedundefined
      • Global variables.
      • undefinedundefined
      • (there are some other, internal ones as well)
      • undefinedundefined
      undefinedundefined

      These values are called undefinedundefinedroots.undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references.

      undefinedundefined

      For instance, if there's an object in a global variable, and that object has a property referencing another object, undefinedundefinedthat object is considered reachable. And those that it references are also reachable. Detailed examples to follow.undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    There's a background process in the JavaScript engine that is called undefinedundefinedgarbage collector. It monitors all objects and removes those that have become unreachable.undefinedundefined

    undefinedundefined

    A simple example

    undefinedundefined

    Here's the simplest example:

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Here the arrow depicts an object reference. The global variable undefinedundefined"user" references the object undefinedundefined{name: "John"} (we'll call it John for brevity). The undefinedundefined"name" property of John stores a primitive, so it's painted inside the object.undefinedundefined

    undefinedundefined

    If the value of undefinedundefineduser is overwritten, the reference is lost:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefineduser undefinedundefined=undefinedundefinednullundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Now John becomes unreachable. There's no way to access it, no references to it. Garbage collector will junk the data and free the memory.

    undefinedundefined

    Two references

    undefinedundefined

    Now let's imagine we copied the reference from undefinedundefineduser to undefinedundefinedadmin:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Now if we do the same:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefineduser undefinedundefined=undefinedundefinednullundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    …Then the object is still reachable via undefinedundefinedadmin global variable, so it's in memory. If we overwrite undefinedundefinedadmin too, then it can be removed.undefinedundefined

    undefinedundefined

    Interlinked objects

    undefinedundefined

    Now a more complex example. The family:

    undefinedundefinedundefinedundefined

    Function undefinedundefinedmarry "marries" two objects by giving them references to each other and returns a new object that contains them both.undefinedundefined

    undefinedundefined

    The resulting memory structure:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    As of now, all objects are reachable.

    undefinedundefined

    Now let's remove two references:

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    It's not enough to delete only one of these two references, because all objects would still be reachable.

    undefinedundefined

    But if we delete both, then we can see that John has no incoming reference any more:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Outgoing references do not matter. Only incoming ones can make an object reachable. So, John is now unreachable and will be removed from the memory with all its data that also became unaccessible.

    undefinedundefined

    After garbage collection:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Unreachable island

    undefinedundefined

    It is possible that the whole island of interlinked objects becomes unreachable and is removed from the memory.

    undefinedundefined

    The source object is the same as above. Then:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedfamily undefinedundefined=undefinedundefinednullundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The in-memory picture becomes:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    This example demonstrates how important the concept of reachability is.

    undefinedundefined

    It's obvious that John and Ann are still linked, both have incoming references. But that's not enough.

    undefinedundefined

    The former undefinedundefined"family" object has been unlinked from the root, there's no reference to it any more, so the whole island becomes unreachable and will be removed.undefinedundefined

    undefinedundefined

    Internal algorithms

    undefinedundefined

    The basic garbage collection algorithm is called "mark-and-sweep".

    undefinedundefined

    The following "garbage collection" steps are regularly performed:

    undefinedundefined
      undefinedundefined
    • The garbage collector takes roots and "marks" (remembers) them.
    • undefinedundefined
    • Then it visits and "marks" all references from them.
    • undefinedundefined
    • Then it visits marked objects and marks undefinedundefinedtheir references. All visited objects are remembered, so as not to visit the same object twice in the future.undefinedundefined
    • undefinedundefined
    • …And so on until every reachable (from the roots) references are visited.
    • undefinedundefined
    • All objects except marked ones are removed.
    • undefinedundefined
    undefinedundefined

    For instance, let our object structure look like this:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    We can clearly see an "unreachable island" to the right side. Now let's see how "mark-and-sweep" garbage collector deals with it.

    undefinedundefined

    The first step marks the roots:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Then their references are marked:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    …And their references, while possible:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Now the objects that could not be visited in the process are considered unreachable and will be removed:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    We can also imagine the process as spilling a huge bucket of paint from the roots, that flows through all references and marks all reachable objects. The unmarked ones are then removed.

    undefinedundefined

    That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not affect the execution.

    undefinedundefined

    Some of the optimizations:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedGenerational collection - objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, they can be cleaned up aggressively. Those that survive for long enough, become "old" and are examined less often.undefinedundefined
    • undefinedundefined
    • undefinedundefinedIncremental collection - if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the garbage collection into pieces. Then the pieces are executed one by one, separately. That requires some extra bookkeeping between them to track changes, but we have many tiny delays instead of a big one.undefinedundefined
    • undefinedundefined
    • undefinedundefinedIdle-time collection - the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution.undefinedundefined
    • undefinedundefined
    undefinedundefined

    There exist other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so studying deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below.

    undefinedundefined

    Summary

    undefinedundefined

    The main things to know:

    undefinedundefined
      undefinedundefined
    • Garbage collection is performed automatically. We cannot force or prevent it.
    • undefinedundefined
    • Objects are retained in memory while they are reachable.
    • undefinedundefined
    • Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
    • undefinedundefined
    undefinedundefined

    Modern engines implement advanced algorithms of garbage collection.

    undefinedundefined

    A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) covers some of them.

    undefinedundefined

    If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article undefinedundefinedA tour of V8: Garbage Collection.undefinedundefined

    undefinedundefined

    undefinedundefinedV8 blog also publishes articles about changes in memory management from time to time. Naturally, to learn the garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of undefinedundefinedVyacheslav Egorov who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects.undefinedundefined

    undefinedundefined

    In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language.

    undefinedundefined

    Object methods, "this"

    undefinedundefined

    Objects are usually created to represent entities of the real world, like users, orders and so on:

    undefinedundefinedundefinedundefined

    And, in the real world, a user can undefinedundefinedact: select something from the shopping cart, login, logout etc.undefinedundefined

    undefinedundefined

    Actions are represented in JavaScript by functions in properties.

    undefinedundefined

    Method examples

    undefinedundefined

    For a start, let's teach the undefinedundefineduser to say hello:undefinedundefined

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    undefinedundefined! user.sayHi = function() { alert("Hello!"); }; undefinedundefined/!undefinedundefined

    undefinedundefined

    user.sayHi(); // Hello!

    undefinedundefined

    Here we've just used a Function Expression to create a function and assign it to the property undefinedundefineduser.sayHi of the object.undefinedundefined

    undefinedundefined

    Then we can call it as undefinedundefineduser.sayHi(). The user can now speak!undefinedundefined

    undefinedundefined

    A function that is a property of an object is called its undefinedundefinedmethod.undefinedundefined

    undefinedundefined

    So, here we've got a method undefinedundefinedsayHi of the object undefinedundefineduser.undefinedundefined

    undefinedundefined

    Of course, we could use a pre-declared function as a method, like this:

    undefinedundefined

    run let user = { // … };

    undefinedundefined

    undefinedundefined! // first, declare function sayHi() { alert("Hello!"); };undefinedundefined

    undefinedundefined

    // then add as a method user.sayHi = sayHi; undefinedundefined/!undefinedundefined

    undefinedundefined

    user.sayHi(); // Hello!

    undefinedundefined

    smart header="Object-oriented programming" When we write our code using objects to represent entities, that's called undefinedundefinedobject-oriented programming, in short: "OOP".undefinedundefined

    undefinedundefined

    OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture, and there are great books on that topic, like "Design Patterns: Elements of Reusable Object-Oriented Software" by E. Gamma, R. Helm, R. Johnson, J. Vissides or "Object-Oriented Analysis and Design with Applications" by G. Booch, and more. ### Method shorthand

    undefinedundefined

    There exists a shorter syntax for methods in an object literal:

    undefinedundefinedundefinedundefined

    As demonstrated, we can omit undefinedundefined"function" and just write undefinedundefinedsayHi().undefinedundefined

    undefinedundefined

    To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred.

    undefinedundefined

    "this" in methods

    undefinedundefined

    It's common that an object method needs to access the information stored in the object to do its job.

    undefinedundefined

    For instance, the code inside undefinedundefineduser.sayHi() may need the name of the undefinedundefineduser.undefinedundefined

    undefinedundefined

    undefinedundefinedTo access the object, a method can use the undefinedundefinedthis keyword.undefinedundefinedundefinedundefined

    undefinedundefined

    The value of undefinedundefinedthis is the object "before dot", the one used to call the method.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let user = { name: "John", age: 30,

    undefinedundefined

    sayHi() { undefinedundefined! // "this" is the "current object" alert(this.name); undefinedundefined/! }undefinedundefined

    undefinedundefined

    };

    undefinedundefined

    user.sayHi(); // John

    undefinedundefined

    Here during the execution of undefinedundefineduser.sayHi(), the value of undefinedundefinedthis will be undefinedundefineduser.undefinedundefined

    undefinedundefined

    Technically, it's also possible to access the object without undefinedundefinedthis, by referencing it via the outer variable:undefinedundefined

    undefinedundefinedundefinedundefined

    …But such code is unreliable. If we decide to copy undefinedundefineduser to another variable, e.g. undefinedundefinedadmin = user and overwrite undefinedundefineduser with something else, then it will access the wrong object.undefinedundefined

    undefinedundefined

    That's demonstrated below:

    undefinedundefined

    run let user = { name: "John", age: 30,

    undefinedundefined

    sayHi() { undefinedundefined! alert( user.name ); // leads to an error undefinedundefined/! }undefinedundefined

    undefinedundefined

    };

    undefinedundefined

    let admin = user; user = null; // overwrite to make things obvious

    undefinedundefined

    undefinedundefined! admin.sayHi(); // TypeError: Cannot read property ‘name' of null undefinedundefined/! undefinedundefined

    undefinedundefined

    If we used undefinedundefinedthis.name instead of undefinedundefineduser.name inside the undefinedundefinedalert, then the code would work.undefinedundefined

    undefinedundefined

    "this" is not bound

    undefinedundefined

    In JavaScript, keyword undefinedundefinedthis behaves unlike most other programming languages. It can be used in any function, even if it's not a method of an object.undefinedundefined

    undefinedundefined

    There's no syntax error in the following example:

    undefinedundefinedundefinedundefined

    The value of undefinedundefinedthis is evaluated during the run-time, depending on the context.undefinedundefined

    undefinedundefined

    For instance, here the same function is assigned to two different objects and has different "this" in the calls:

    undefinedundefined

    run let user = { name: "John" }; let admin = { name: "Admin" };

    undefinedundefined

    function sayHi() { alert( this.name ); }

    undefinedundefined

    undefinedundefined! // use the same function in two objects user.f = sayHi; admin.f = sayHi; undefinedundefined/!undefinedundefined

    undefinedundefined

    // these calls have different this // "this" inside the function is the object "before the dot" user.f(); // John (this == user) admin.f(); // Admin (this == admin)

    undefinedundefined

    adminundefinedundefined‘f'; // Admin (dot or square brackets access the method - doesn't matter) undefinedundefined

    undefinedundefined

    The rule is simple: if undefinedundefinedobj.f() is called, then undefinedundefinedthis is undefinedundefinedobj during the call of undefinedundefinedf. So it's either undefinedundefineduser or undefinedundefinedadmin in the example above.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Calling without an object:this == undefined`" We can even call the function without an object at all:undefinedundefined

    undefinedundefined

    run function sayHi() { alert(this); }

    undefinedundefined

    sayHi(); // undefined

    undefinedundefined
    undefinedundefined
    In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error.
    In non-strict mode the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later in the chapter [](info:global-object)). This is a historical behavior that `"use strict"` fixes.
    Usually such call is a programming error. If there's `this` inside a function, it expects to be called in an object context.undefinedundefined
    undefinedundefined

    ``undefinedundefinedsmart header="The consequences of unboundthisundefinedundefined" If you come from another programming language, then you are probably used to the idea of a "boundthisundefinedundefined", where methods defined in an object always havethis` referencing that object.undefinedundefined

    undefinedundefined

    In JavaScript undefinedundefinedthis is "free", its value is evaluated at call-time and does not depend on where the method was declared, but rather on what object is "before the dot".undefinedundefined

    undefinedundefined

    The concept of run-time evaluated undefinedundefinedthis has both pluses and minuses. On the one hand, a function can be reused for different objects. On the other hand, the greater flexibility creates more possibilities for mistakes.undefinedundefined

    undefinedundefined

    Here our position is not to judge whether this language design decision is good or bad. We'll understand how to work with it, how to get benefits and avoid problems.

    undefinedundefined

    Arrow functions have no "this"

    undefinedundefined

    Arrow functions are special: they don't have their "own" undefinedundefinedthis. If we reference undefinedundefinedthis from such a function, it's taken from the outer "normal" function.undefinedundefined

    undefinedundefined

    For instance, here undefinedundefinedarrow() uses undefinedundefinedthis from the outer undefinedundefineduser.sayHi() method:undefinedundefined

    undefinedundefined

    run let user = { firstName: "Ilya", sayHi() { let arrow = () => alert(this.firstName); arrow(); } };

    undefinedundefined

    user.sayHi(); // Ilya

    undefinedundefined

    That's a special feature of arrow functions, it's useful when we actually do not want to have a separate undefinedundefinedthis, but rather to take it from the outer context. Later in the chapter undefinedundefinedinfo:arrow-functions we'll go more deeply into arrow functions.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Functions that are stored in object properties are called "methods".
    • undefinedundefined
    • Methods allow objects to "act" like undefinedundefinedobject.doSomething().undefinedundefined
    • undefinedundefined
    • Methods can reference the object as undefinedundefinedthis.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The value of undefinedundefinedthis is defined at run-time. - When a function is declared, it may use undefinedundefinedthis, but that undefinedundefinedthis has no value until the function is called. - A function can be copied between objects. - When a function is called in the "method" syntax: undefinedundefinedobject.method(), the value of undefinedundefinedthis during the call is undefinedundefinedobject.undefinedundefined

    undefinedundefined

    Please note that arrow functions are special: they have no undefinedundefinedthis. When undefinedundefinedthis is accessed inside an arrow function, it is taken from outside.undefinedundefined

    undefinedundefined

    Constructor, operator "new"

    undefinedundefined

    The regular undefinedundefined{...} syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.undefinedundefined

    undefinedundefined

    That can be done using constructor functions and the undefinedundefined"new" operator.undefinedundefined

    undefinedundefined

    Constructor function

    undefinedundefined

    Constructor functions technically are regular functions. There are two conventions though:

    undefinedundefined
      undefinedundefined
    1. They are named with capital letter first.
    2. undefinedundefined
    3. They should be executed only with undefinedundefined"new" operator.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run function User(name) { this.name = name; this.isAdmin = false; }

    undefinedundefined

    undefinedundefined! let user = new User("Jack"); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(user.name); // Jack alert(user.isAdmin); // false

    undefinedundefined

    When a function is executed with undefinedundefinednew, it does the following steps:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. A new empty object is created and assigned to undefinedundefinedthis.undefinedundefined
    2. undefinedundefined
    3. The function body executes. Usually it modifies undefinedundefinedthis, adds new properties to it.undefinedundefined
    4. undefinedundefined
    5. The value of undefinedundefinedthis is returned.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    In other words, undefinedundefinednew User(...) does something like:undefinedundefined

    undefinedundefinedundefinedundefined

    So undefinedundefinedlet user = new User("Jack") gives the same result as:undefinedundefined

    undefinedundefinedundefinedundefined

    Now if we want to create other users, we can call undefinedundefinednew User("Ann"), undefinedundefinednew User("Alice") and so on. Much shorter than using literals every time, and also easy to read.undefinedundefined

    undefinedundefined

    That's the main purpose of constructors - to implement reusable object creation code.

    undefinedundefined

    Let's note once again - technically, any function can be used as a constructor. That is: any function can be run with undefinedundefinednew, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with undefinedundefinednew.undefinedundefined

    undefinedundefined

    smart header="new function() { … }" If we have many lines of code all about creation of a single complex object, we can wrap them in constructor function, like this:

    undefinedundefined undefinedundefined

    The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse.

    undefinedundefined

    Constructor mode test: new.target

    undefinedundefined

    undefinedundefinedsmart header="Advanced stuff" The syntax from this section is rarely used, skip it unless you want to know everything.undefinedundefined

    undefinedundefined

    Inside a function, we can check whether it was called with undefinedundefinednew or without it, using a special undefinedundefinednew.target property.undefinedundefined

    undefinedundefined

    It is undefined for regular calls and equals the function if called with undefinedundefinednew:undefinedundefined

    undefinedundefined

    run function User() { alert(new.target); }

    undefinedundefined

    // without "new": undefinedundefined! User(); // undefined undefinedundefined/!undefinedundefined

    undefinedundefined

    // with "new": undefinedundefined! new User(); // function User { … } undefinedundefined/! undefinedundefined

    undefinedundefined

    That can be used inside the function to know whether it was called with undefinedundefinednew, "in constructor mode", or without it, "in regular mode".undefinedundefined

    undefinedundefined

    We can also make both undefinedundefinednew and regular calls to do the same, like this:undefinedundefined

    undefinedundefined

    run function User(name) { if (!new.target) { // if you run me without new return new User(name); // …I will add new for you }

    undefinedundefined

    this.name = name; }

    undefinedundefined

    let john = User("John"); // redirects call to new User alert(john.name); // John

    undefinedundefined

    This approach is sometimes used in libraries to make the syntax more flexible. So that people may call the function with or without undefinedundefinednew, and it still works.undefinedundefined

    undefinedundefined

    Probably not a good thing to use everywhere though, because omitting undefinedundefinednew makes it a bit less obvious what's going on. With undefinedundefinednew we all know that the new object is being created.undefinedundefined

    undefinedundefined

    Return from constructors

    undefinedundefined

    Usually, constructors do not have a undefinedundefinedreturn statement. Their task is to write all necessary stuff into undefinedundefinedthis, and it automatically becomes the result.undefinedundefined

    undefinedundefined

    But if there is a undefinedundefinedreturn statement, then the rule is simple:undefinedundefined

    undefinedundefined
      undefinedundefined
    • If undefinedundefinedreturn is called with an object, then the object is returned instead of undefinedundefinedthis.undefinedundefined
    • undefinedundefined
    • If undefinedundefinedreturn is called with a primitive, it's ignored.undefinedundefined
    • undefinedundefined
    undefinedundefined

    In other words, undefinedundefinedreturn with an object returns that object, in all other cases undefinedundefinedthis is returned.undefinedundefined

    undefinedundefined

    For instance, here undefinedundefinedreturn overrides undefinedundefinedthis by returning an object:undefinedundefined

    undefinedundefined

    run function BigUser() {

    undefinedundefined

    this.name = "John";

    undefinedundefined

    return { name: "Godzilla" }; // <- returns this object }

    undefinedundefined

    alert( new BigUser().name ); // Godzilla, got that object

    undefinedundefined

    And here's an example with an empty undefinedundefinedreturn (or we could place a primitive after it, doesn't matter):undefinedundefined

    undefinedundefined

    run function SmallUser() {

    undefinedundefined

    this.name = "John";

    undefinedundefined

    return; // <- returns this }

    undefinedundefined

    alert( new SmallUser().name ); // John

    undefinedundefined

    Usually constructors don't have a undefinedundefinedreturn statement. Here we mention the special behavior with returning objects mainly for the sake of completeness.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Omitting parentheses" By the way, we can omit parentheses afternew`, if it has no arguments:undefinedundefined

    undefinedundefined undefinedundefined

    Omitting parentheses here is not considered a "good style", but the syntax is permitted by specification.

    undefinedundefined

    Methods in constructor

    undefinedundefined

    Using constructor functions to create objects gives a great deal of flexibility. The constructor function may have parameters that define how to construct the object, and what to put in it.

    undefinedundefined

    Of course, we can add to undefinedundefinedthis not only properties, but methods as well.undefinedundefined

    undefinedundefined

    For instance, undefinedundefinednew User(name) below creates an object with the given undefinedundefinedname and the method undefinedundefinedsayHi:undefinedundefined

    undefinedundefined

    run function User(name) { this.name = name;

    undefinedundefined

    this.sayHi = function() { alert( "My name is:" + this.name ); }; }

    undefinedundefined

    undefinedundefined! let john = new User("John");undefinedundefined

    undefinedundefined

    john.sayHi(); // My name is: John undefinedundefined/!undefinedundefined

    undefinedundefined

    /undefinedundefined john = { name: "John", sayHi: function() { … } } / undefinedundefined

    undefinedundefined

    To create complex objects, there's a more advanced syntax, undefinedundefinedclasses, that we'll cover later.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Constructor functions or, briefly, constructors, are regular functions, but there's a common agreement to name them with capital letter first.
    • undefinedundefined
    • Constructor functions should only be called using undefinedundefinednew. Such a call implies a creation of empty undefinedundefinedthis at the start and returning the populated one at the end.undefinedundefined
    • undefinedundefined
    undefinedundefined

    We can use constructor functions to make multiple similar objects.

    undefinedundefined

    JavaScript provides constructor functions for many built-in language objects: like undefinedundefinedDate for dates, undefinedundefinedSet for sets and others that we plan to study.undefinedundefined

    undefinedundefined

    smart header="Objects, we'll be back!" In this chapter we only cover the basics about objects and constructors. They are essential for learning more about data types and functions in the next chapters.

    undefinedundefined

    After we learn that, we return to objects and cover them in-depth in the chapters undefinedundefinedinfo:prototypes and undefinedundefinedinfo:classes. undefinedundefined

    undefinedundefined

    Optional chaining ‘?.'''

    undefinedundefined

    [recent browser="new"]

    undefinedundefined

    The optional chaining undefinedundefined?. is a safe way to access nested object properties, even if an intermediate property doesn't exist.undefinedundefined

    undefinedundefined

    The "non-existing property" problem

    undefinedundefined

    If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common.

    undefinedundefined

    As an example, let's say we have undefinedundefineduser objects that hold the information about our users.undefinedundefined

    undefinedundefined

    Most of our users have addresses in undefinedundefineduser.address property, with the street undefinedundefineduser.address.street, but some did not provide them.undefinedundefined

    undefinedundefined

    In such case, when we attempt to get undefinedundefineduser.address.street, and the user happens to be without an address, we get an error:undefinedundefined

    undefinedundefined

    run let user = {}; // a user without "address" property

    undefinedundefined

    alert(user.address.street); // Error!

    undefinedundefined

    That's the expected result. JavaScript works like this. As undefinedundefineduser.address is undefinedundefinedundefined, an attempt to get undefinedundefineduser.address.street fails with an error.undefinedundefined

    undefinedundefined

    In many practical cases we'd prefer to get undefinedundefinedundefined instead of an error here (meaning "no street").undefinedundefined

    undefinedundefined

    …And another example. In the web development, we can get an object that corresponds to a web page element using a special method call, such as undefinedundefineddocument.querySelector('.elem'), and it returns undefinedundefinednull when there's no such element.undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // document.querySelector('.elem') is null if there's no element let html = document.querySelector('.elem').innerHTML; // error if it's nullundefinedundefined

    undefinedundefined

    Once again, if the element doesn't exist, we'll get an error accessing undefinedundefined.innerHTML of undefinedundefinednull. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept undefinedundefinedhtml = null as the result.undefinedundefined

    undefinedundefined

    How can we do this?

    undefinedundefined

    The obvious solution would be to check the value using undefinedundefinedif or the conditional operator undefinedundefined?, before accessing its property, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    It works, there's no error… But it's quite inelegant. As you can see, the undefinedundefined"user.address" appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required.undefinedundefined

    undefinedundefined

    E.g. let's try getting undefinedundefineduser.address.street.name.undefinedundefined

    undefinedundefined

    We need to check both undefinedundefineduser.address and undefinedundefineduser.address.street:undefinedundefined

    undefinedundefinedundefinedundefined

    That's just awful, one may even have problems understanding such code.

    undefinedundefined

    Don't even care to, as there's a better way to write it, using the undefinedundefined&& operator:undefinedundefined

    undefinedundefined

    run let user = {}; // user has no address

    undefinedundefined

    alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)

    undefinedundefined

    AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal.

    undefinedundefined

    As you can see, property names are still duplicated in the code. E.g. in the code above, undefinedundefineduser.address appears three times.undefinedundefined

    undefinedundefined

    That's why the optional chaining undefinedundefined?. was added to the language. To solve this problem once and for all!undefinedundefined

    undefinedundefined

    Optional chaining

    undefinedundefined

    The optional chaining undefinedundefined?. stops the evaluation if the value before undefinedundefined?. is undefinedundefinedundefined or undefinedundefinednull and returns undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    undefinedundefinedFurther in this article, for brevity, we'll be saying that something "exists" if it's not undefinedundefinednull and not undefinedundefinedundefined.undefinedundefinedundefinedundefined

    undefinedundefined

    In other words, undefinedundefinedvalue?.prop: - works as undefinedundefinedvalue.prop, if undefinedundefinedvalue exists, - otherwise (when undefinedundefinedvalue is undefinedundefinedundefined/null) it returns undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    Here's the safe way to access undefinedundefineduser.address.street using undefinedundefined?.:undefinedundefined

    undefinedundefined

    run let user = {}; // user has no address

    undefinedundefined

    alert( user?.address?.street ); // undefined (no error)

    undefinedundefined

    The code is short and clean, there's no duplication at all.

    undefinedundefined

    Reading the address with undefinedundefineduser?.address works even if undefinedundefineduser object doesn't exist:undefinedundefined

    undefinedundefined

    run let user = null;

    undefinedundefined

    alert( user?.address ); // undefined alert( user?.address.street ); // undefined

    undefinedundefined

    Please note: the undefinedundefined?. syntax makes optional the value before it, but not any further.undefinedundefined

    undefinedundefined

    E.g. in undefinedundefineduser?.address.street.name the undefinedundefined?. allows undefinedundefineduser to safely be undefinedundefinednull/undefined (and returns undefinedundefinedundefined in that case), but that's only for undefinedundefineduser. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more undefinedundefined. with undefinedundefined?..undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="Don't overuse the optional chaining" We should use?.` only where it's ok that something doesn't exist.undefinedundefined

    undefinedundefined

    For example, if according to our coding logic undefinedundefineduser object must exist, but undefinedundefinedaddress is optional, then we should write undefinedundefineduser.address?.street, but not undefinedundefineduser?.address?.street.undefinedundefined

    undefinedundefined

    So, if undefinedundefineduser happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug. undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="The variable before?.undefinedundefinedmust be declared" If there's no variableuserundefinedundefinedat all, thenuser?.anything` triggers an error:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // ReferenceError: user is not defined user?.address; The variable must be declared (e.g. undefinedundefinedlet/const/var user or as a function parameter). The optional chaining works only for declared variables. undefinedundefined

    undefinedundefined

    Short-circuiting

    undefinedundefined

    As it was said before, the undefinedundefined?. immediately stops ("short-circuits") the evaluation if the left part doesn't exist.undefinedundefined

    undefinedundefined

    So, if there are any further function calls or side effects, they don't occur.

    undefinedundefined

    For instance:

    undefinedundefined

    run let user = null; let x = 0;

    undefinedundefined

    user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++

    undefinedundefined

    alert(x); // 0, value not incremented

    undefinedundefined

    Other variants: ?.(), ?.[]

    undefinedundefined

    The optional chaining undefinedundefined?. is not an operator, but a special syntax construct, that also works with functions and square brackets.undefinedundefined

    undefinedundefined

    For example, undefinedundefined?.() is used to call a function that may not exist.undefinedundefined

    undefinedundefined

    In the code below, some of our users have undefinedundefinedadmin method, and some don't:undefinedundefined

    undefinedundefined

    run let userAdmin = { admin() { alert("I am admin"); } };

    undefinedundefined

    let userGuest = {};

    undefinedundefined

    undefinedundefined! userAdmin.admin?.(); // I am admin undefinedundefined/!undefinedundefined

    undefinedundefined

    undefinedundefined! userGuest.admin?.(); // nothing (no such method) undefinedundefined/! undefinedundefined

    undefinedundefined

    Here, in both lines we first use the dot (undefinedundefineduserAdmin.admin) to get undefinedundefinedadmin property, because we assume that the user object exists, so it's safe read from it.undefinedundefined

    undefinedundefined

    Then undefinedundefined?.() checks the left part: if the admin function exists, then it runs (that's so for undefinedundefineduserAdmin). Otherwise (for undefinedundefineduserGuest) the evaluation stops without errors.undefinedundefined

    undefinedundefined

    The undefinedundefined?.[] syntax also works, if we'd like to use brackets undefinedundefined[] to access properties instead of dot undefinedundefined.. Similar to previous cases, it allows to safely read a property from an object that may not exist.undefinedundefined

    undefinedundefined

    run let key = "firstName";

    undefinedundefined

    let user1 = { firstName: "John" };

    undefinedundefined

    let user2 = null;

    undefinedundefined

    alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined

    undefinedundefined

    Also we can use undefinedundefined?. with undefinedundefineddelete:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run delete user?.name; // delete user.name if user existsundefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="We can use?.undefinedundefinedfor safe reading and deleting, but not writing" The optional chaining?.` has no use at the left side of an assignment.undefinedundefined

    undefinedundefined

    For example: run let user = null;

    undefinedundefined

    user?.name = "John"; // Error, doesn't work // because it evaluates to undefined = "John"

    undefinedundefined
    undefinedundefined
    It's just not that smart.undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    The optional chaining undefinedundefined?. syntax has three forms:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedobj?.prop - returns undefinedundefinedobj.prop if undefinedundefinedobj exists, otherwise undefinedundefinedundefined.undefinedundefined
    2. undefinedundefined
    3. undefinedundefinedobj?.[prop] - returns undefinedundefinedobj[prop] if undefinedundefinedobj exists, otherwise undefinedundefinedundefined.undefinedundefined
    4. undefinedundefined
    5. undefinedundefinedobj.method?.() - calls undefinedundefinedobj.method() if undefinedundefinedobj.method exists, otherwise returns undefinedundefinedundefined.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    As we can see, all of them are straightforward and simple to use. The undefinedundefined?. checks the left part for undefinedundefinednull/undefined and allows the evaluation to proceed if it's not so.undefinedundefined

    undefinedundefined

    A chain of undefinedundefined?. allows to safely access nested properties.undefinedundefined

    undefinedundefined

    Still, we should apply undefinedundefined?. carefully, only where it's acceptable that the left part doesn't exist. So that it won't hide programming errors from us, if they occur.undefinedundefined

    undefinedundefined

    Symbol type

    undefinedundefined

    By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types.

    undefinedundefined

    Till now we've been using only strings. Now let's see the benefits that symbols can give us.

    undefinedundefined

    Symbols

    undefinedundefined

    A "symbol" represents a unique identifier.

    undefinedundefined

    A value of this type can be created using undefinedundefinedSymbol():undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// id is a new symbolundefinedundefinedundefinedundefinedundefinedundefinedlet id undefinedundefined=undefinedundefinedSymbol()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes:

    undefinedundefinedundefinedundefined

    Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn't affect anything.

    undefinedundefined

    For instance, here are two symbols with the same description - they are not equal:

    undefinedundefined

    run let id1 = Symbol("id"); let id2 = Symbol("id");

    undefinedundefined

    undefinedundefined! alert(id1 == id2); // false undefinedundefined/! undefinedundefined

    undefinedundefined

    If you are familiar with Ruby or another language that also has some sort of "symbols" - please don't be misguided. JavaScript symbols are different.

    undefinedundefined

    undefinedundefinedwarn header="Symbols don't auto-convert to a string" Most values in JavaScript support implicit conversion to a string. For instance, we canalert` almost any value, and it will work. Symbols are special. They don't auto-convert.undefinedundefined

    undefinedundefined

    For instance, this undefinedundefinedalert will show an error:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let id = Symbol("id"); *!* alert(id); // TypeError: Cannot convert a Symbol value to a string */!*undefinedundefined

    undefinedundefined

    That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another.

    undefinedundefined

    If we really want to show a symbol, we need to explicitly call undefinedundefined.toString() on it, like here: undefinedundefinedjs run let id = Symbol("id"); *!* alert(id.toString()); // Symbol(id), now it works */!*undefinedundefined

    undefinedundefined

    Or get undefinedundefinedsymbol.description property to show the description only: undefinedundefinedjs run let id = Symbol("id"); *!* alert(id.description); // id */!*undefinedundefined

    undefinedundefined

    undefinedundefined

    "Hidden" properties

    undefinedundefined

    Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite.

    undefinedundefined

    For instance, if we're working with undefinedundefineduser objects, that belong to a third-party code. We'd like to add identifiers to them.undefinedundefined

    undefinedundefined

    Let's use a symbol key for it:

    undefinedundefined

    run let user = { // belongs to another code name: "John" };

    undefinedundefined

    let id = Symbol("id");

    undefinedundefined

    user[id] = 1;

    undefinedundefined

    alert( user[id] ); // we can access the data using the symbol as the key

    undefinedundefined

    What's the benefit of using undefinedundefinedSymbol("id") over a string undefinedundefined"id"?undefinedundefined

    undefinedundefined

    As undefinedundefineduser objects belongs to another code, and that code also works with them, we shouldn't just add any fields to it. That's unsafe. But a symbol cannot be accessed accidentally, the third-party code probably won't even see it, so it's probably all right to do.undefinedundefined

    undefinedundefined

    Also, imagine that another script wants to have its own identifier inside undefinedundefineduser, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other.undefinedundefined

    undefinedundefined

    Then that script can create its own undefinedundefinedSymbol("id"), like this:undefinedundefined

    undefinedundefinedundefinedundefined

    There will be no conflict between our and their identifiers, because symbols are always different, even if they have the same name.

    undefinedundefined

    …But if we used a string undefinedundefined"id" instead of a symbol for the same purpose, then there undefinedundefinedwould be a conflict:undefinedundefined

    undefinedundefinedundefinedundefined

    Symbols in an object literal

    undefinedundefined

    If we want to use a symbol in an object literal undefinedundefined{...}, we need square brackets around it.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefinedundefinedundefined

    That's because we need the value from the variable undefinedundefinedid as the key, not the string "id".undefinedundefined

    undefinedundefined

    Symbols are skipped by for..in

    undefinedundefined

    Symbolic properties do not participate in undefinedundefinedfor..in loop.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 };

    undefinedundefined

    undefinedundefined! for (let key in user) alert(key); // name, age (no symbols) undefinedundefined/!undefinedundefined

    undefinedundefined

    // the direct access by the symbol works alert( "Direct:" + user[id] );

    undefinedundefined

    undefinedundefinedObject.keys(user) also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.undefinedundefined

    undefinedundefined

    In contrast, undefinedundefinedObject.assign copies both string and symbol properties:undefinedundefined

    undefinedundefined

    run let id = Symbol("id"); let user = { [id]: 123 };

    undefinedundefined

    let clone = Object.assign({}, user);

    undefinedundefined

    alert( clone[id] ); // 123

    undefinedundefined

    There's no paradox here. That's by design. The idea is that when we clone an object or merge objects, we usually want undefinedundefinedall properties to be copied (including symbols like undefinedundefinedid).undefinedundefined

    undefinedundefined

    Global symbols

    undefinedundefined

    As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol undefinedundefined"id" meaning exactly the same property.undefinedundefined

    undefinedundefined

    To achieve that, there exists a undefinedundefinedglobal symbol registry. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.undefinedundefined

    undefinedundefined

    In order to read (create if absent) a symbol from the registry, use undefinedundefinedSymbol.for(key).undefinedundefined

    undefinedundefined

    That call checks the global registry, and if there's a symbol described as undefinedundefinedkey, then returns it, otherwise creates a new symbol undefinedundefinedSymbol(key) and stores it in the registry by the given undefinedundefinedkey.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run // read from the global registry let id = Symbol.for("id"); // if the symbol did not exist, it is created

    undefinedundefined

    // read it again (maybe from another part of the code) let idAgain = Symbol.for("id");

    undefinedundefined

    // the same symbol alert( id === idAgain ); // true

    undefinedundefined

    Symbols inside the registry are called undefinedundefinedglobal symbols. If we want an application-wide symbol, accessible everywhere in the code - that's what they are for.undefinedundefined

    undefinedundefined

    smart header="That sounds like Ruby" In some programming languages, like Ruby, there's a single symbol per name.

    undefinedundefined

    In JavaScript, as we can see, that's right for global symbols.

    undefinedundefined

    Symbol.keyFor

    undefinedundefined

    For global symbols, not only undefinedundefinedSymbol.for(key) returns a symbol by name, but there's a reverse call: undefinedundefinedSymbol.keyFor(sym), that does the reverse: returns a name by a global symbol.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run // get symbol by name let sym = Symbol.for("name"); let sym2 = Symbol.for("id");

    undefinedundefined

    // get name by symbol alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id

    undefinedundefined

    The undefinedundefinedSymbol.keyFor internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and returns undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    That said, any symbols have undefinedundefineddescription property.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name");

    undefinedundefined

    alert( Symbol.keyFor(globalSymbol) ); // name, global symbol alert( Symbol.keyFor(localSymbol) ); // undefined, not global

    undefinedundefined

    alert( localSymbol.description ); // name

    undefinedundefined

    System symbols

    undefinedundefined

    There exist many "system" symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects.

    undefinedundefined

    They are listed in the specification in the undefinedundefinedWell-known symbols table:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedSymbol.hasInstanceundefinedundefined
    • undefinedundefined
    • undefinedundefinedSymbol.isConcatSpreadableundefinedundefined
    • undefinedundefined
    • undefinedundefinedSymbol.iteratorundefinedundefined
    • undefinedundefined
    • undefinedundefinedSymbol.toPrimitiveundefinedundefined
    • undefinedundefined
    • …and so on.
    • undefinedundefined
    undefinedundefined

    For instance, undefinedundefinedSymbol.toPrimitive allows us to describe object to primitive conversion. We'll see its use very soon.undefinedundefined

    undefinedundefined

    Other symbols will also become familiar when we study the corresponding language features.

    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedSymbol is a primitive type for unique identifiers.undefinedundefined

    undefinedundefined

    Symbols are created with undefinedundefinedSymbol() call with an optional description (name).undefinedundefined

    undefinedundefined

    Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: undefinedundefinedSymbol.for(key) returns (creates if needed) a global symbol with undefinedundefinedkey as the name. Multiple calls of undefinedundefinedSymbol.for with the same undefinedundefinedkey return exactly the same symbol.undefinedundefined

    undefinedundefined

    Symbols have two main use cases:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      "Hidden" object properties. If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in undefinedundefinedfor..in, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite.undefinedundefined

      undefinedundefined

      So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties.

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      There are many system symbols used by JavaScript which are accessible as undefinedundefinedSymbol.*. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use undefinedundefinedSymbol.iterator for undefinedundefinediterables, undefinedundefinedSymbol.toPrimitive to setup undefinedundefinedobject-to-primitive conversion and so on.undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Technically, symbols are not 100% hidden. There is a built-in method undefinedundefinedObject.getOwnPropertySymbols(obj) that allows us to get all symbols. Also there is a method named undefinedundefinedReflect.ownKeys(obj) that returns undefinedundefinedall keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods.undefinedundefined

    undefinedundefined

    Object to primitive conversion

    undefinedundefined

    What happens when objects are added undefinedundefinedobj1 + obj2, subtracted undefinedundefinedobj1 - obj2 or printed using undefinedundefinedalert(obj)?undefinedundefined

    undefinedundefined

    In that case, objects are auto-converted to primitives, and then the operation is carried out.

    undefinedundefined

    In the chapter undefinedundefinedinfo:type-conversions we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.undefinedundefined

    undefinedundefined
      undefinedundefined
    1. All objects are undefinedundefinedtrue in a boolean context. There are only numeric and string conversions.undefinedundefined
    2. undefinedundefined
    3. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, undefinedundefinedDate objects (to be covered in the chapter undefinedundefinedinfo:date) can be subtracted, and the result of undefinedundefineddate1 - date2 is the time difference between two dates.undefinedundefined
    4. undefinedundefined
    5. As for the string conversion - it usually happens when we output an object like undefinedundefinedalert(obj) and in similar contexts.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    ToPrimitive

    undefinedundefined

    We can fine-tune string and numeric conversion, using special object methods.

    undefinedundefined

    There are three variants of type conversion, so-called "hints", described in the undefinedundefinedspecification:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined"string"undefinedundefined
    undefinedundefined
    undefinedundefined

    For an object-to-string conversion, when we're doing an operation on an object that expects a string, like undefinedundefinedalert:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// outputundefinedundefinedundefinedundefinedundefinedundefinedalert(obj)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// using object as a property keyundefinedundefinedundefinedundefinedanotherObj[obj] undefinedundefined=undefinedundefined123undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefined"number"undefinedundefined
    undefinedundefined
    undefinedundefined

    For an object-to-number conversion, like when we're doing maths:

    undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined"default"undefinedundefined
    undefinedundefined
    undefinedundefined

    Occurs in rare cases when the operator is "not sure" what type to expect.

    undefinedundefined

    For instance, binary plus undefinedundefined+ can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the undefinedundefined"default" hint to convert it.undefinedundefined

    undefinedundefined

    Also, if an object is compared using undefinedundefined== with a string, number or a symbol, it's also unclear which conversion should be done, so the undefinedundefined"default" hint is used.undefinedundefined

    undefinedundefinedundefinedundefined

    The greater and less comparison operators, such as undefinedundefined<undefinedundefined>, can work with both strings and numbers too. Still, they use the undefinedundefined"number" hint, not undefinedundefined"default". That's for historical reasons.undefinedundefined

    undefinedundefined

    In practice though, we don't need to remember these peculiar details, because all built-in objects except for one case (undefinedundefinedDate object, we'll learn it later) implement undefinedundefined"default" conversion the same way as undefinedundefined"number". And we can do the same.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    ``undefinedundefinedsmart header="No"boolean"` hint" Please note - there are only three hints. It's that simple.undefinedundefined

    undefinedundefined

    There is no "boolean" hint (all objects are undefinedundefinedtrue in boolean context) or anything else. And if we treat undefinedundefined"default" and undefinedundefined"number" the same, like most built-ins do, then there are only two conversions. undefinedundefined

    undefinedundefined

    undefinedundefinedTo do the conversion, JavaScript tries to find and call three object methods:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Call undefinedundefinedobj[Symbol.toPrimitive](hint) - the method with the symbolic key undefinedundefinedSymbol.toPrimitive (system symbol), if such method exists,undefinedundefined
    2. undefinedundefined
    3. Otherwise if hint is undefinedundefined"string"undefinedundefined
        undefinedundefined
      • try undefinedundefinedobj.toString() and undefinedundefinedobj.valueOf(), whatever exists.undefinedundefined
      • undefinedundefined
      undefinedundefined
    4. undefinedundefined
    5. Otherwise if hint is undefinedundefined"number" or undefinedundefined"default"undefinedundefined
        undefinedundefined
      • try undefinedundefinedobj.valueOf() and undefinedundefinedobj.toString(), whatever exists.undefinedundefined
      • undefinedundefined
      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Symbol.toPrimitive

    undefinedundefined

    Let's start from the first method. There's a built-in symbol named undefinedundefinedSymbol.toPrimitive that should be used to name the conversion method, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    For instance, here undefinedundefineduser object implements it:undefinedundefined

    undefinedundefined

    run let user = { name: "John", money: 1000,

    undefinedundefined

    undefinedundefinedSymbol.toPrimitive { alert(undefinedundefinedhint: ${hint}); return hint == "string" ? undefinedundefined{name: "${this.name}"} : this.money; } };undefinedundefined

    undefinedundefined

    // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500

    undefinedundefined

    As we can see from the code, undefinedundefineduser becomes a self-descriptive string or a money amount depending on the conversion. The single method undefinedundefineduser[Symbol.toPrimitive] handles all conversion cases.undefinedundefined

    undefinedundefined

    toString/valueOf

    undefinedundefined

    Methods undefinedundefinedtoString and undefinedundefinedvalueOf come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion.undefinedundefined

    undefinedundefined

    If there's no undefinedundefinedSymbol.toPrimitive then JavaScript tries to find them and try in the order:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedtoString -> valueOf for "string" hint.undefinedundefined
    • undefinedundefined
    • undefinedundefinedvalueOf -> toString otherwise.undefinedundefined
    • undefinedundefined
    undefinedundefined

    These methods must return a primitive value. If undefinedundefinedtoString or undefinedundefinedvalueOf returns an object, then it's ignored (same as if there were no method).undefinedundefined

    undefinedundefined

    By default, a plain object has following undefinedundefinedtoString and undefinedundefinedvalueOf methods:undefinedundefined

    undefinedundefined
      undefinedundefined
    • The undefinedundefinedtoString method returns a string undefinedundefined"[object Object]".undefinedundefined
    • undefinedundefined
    • The undefinedundefinedvalueOf method returns the object itself.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Here's the demo:

    undefinedundefined

    run let user = {name: "John"};

    undefinedundefined

    alert(user); // [object Object] alert(user.valueOf() === user); // true

    undefinedundefined

    So if we try to use an object as a string, like in an undefinedundefinedalert or so, then by default we see undefinedundefined[object Object].undefinedundefined

    undefinedundefined

    And the default undefinedundefinedvalueOf is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist.undefinedundefined

    undefinedundefined

    Let's implement these methods.

    undefinedundefined

    For instance, here undefinedundefineduser does the same as above using a combination of undefinedundefinedtoString and undefinedundefinedvalueOf instead of undefinedundefinedSymbol.toPrimitive:undefinedundefined

    undefinedundefined

    run let user = { name: "John", money: 1000,

    undefinedundefined

    // for hint="string" toString() { return undefinedundefined{name: "${this.name}"}; },undefinedundefined

    undefinedundefined

    // for hint="number" or "default" valueOf() { return this.money; }

    undefinedundefined

    };

    undefinedundefined

    alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500

    undefinedundefined

    As we can see, the behavior is the same as the previous example with undefinedundefinedSymbol.toPrimitive.undefinedundefined

    undefinedundefined

    Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement undefinedundefinedtoString only, like this:undefinedundefined

    undefinedundefined

    run let user = { name: "John",

    undefinedundefined

    toString() { return this.name; } };

    undefinedundefined

    alert(user); // toString -> John alert(user + 500); // toString -> John500

    undefinedundefined

    In the absence of undefinedundefinedSymbol.toPrimitive and undefinedundefinedvalueOf, undefinedundefinedtoString will handle all primitive conversions.undefinedundefined

    undefinedundefined

    Return types

    undefinedundefined

    The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.

    undefinedundefined

    There is no control whether undefinedundefinedtoString returns exactly a string, or whether undefinedundefinedSymbol.toPrimitive method returns a number for a hint undefinedundefined"number".undefinedundefined

    undefinedundefined

    The only mandatory thing: these methods must return a primitive, not an object.

    undefinedundefined

    ``undefinedundefinedsmart header="Historical notes" For historical reasons, iftoStringundefinedundefinedorvalueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.undefinedundefined

    undefinedundefined

    In contrast, undefinedundefinedSymbol.toPrimitiveundefinedundefinedmust return a primitive, otherwise there will be an error. undefinedundefined

    undefinedundefined

    Further conversions

    undefinedundefined

    As we know already, many operators and functions perform type conversions, e.g. multiplication undefinedundefined* converts operands to numbers.undefinedundefined

    undefinedundefined

    If we pass an object as an argument, then there are two stages: 1. The object is converted to a primitive (using the rules described above). 2. If the resulting primitive isn't of the right type, it's converted.

    undefinedundefined

    For instance:

    undefinedundefined

    run let obj = { // toString handles all conversions in the absence of other methods toString() { return "2"; } };

    undefinedundefined

    alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number

    undefinedundefined
      undefinedundefined
    1. The multiplication undefinedundefinedobj * 2 first converts the object to primitive (that's a string undefinedundefined"2").undefinedundefined
    2. undefinedundefined
    3. Then undefinedundefined"2" * 2 becomes undefinedundefined2 * 2 (the string is converted to number).undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Binary plus will concatenate strings in the same situation, as it gladly accepts a string:

    undefinedundefined

    run let obj = { toString() { return "2"; } };

    undefinedundefined

    alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation

    undefinedundefined

    Summary

    undefinedundefined

    The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value.

    undefinedundefined

    There are 3 types (hints) of it: - undefinedundefined"string" (for undefinedundefinedalert and other operations that need a string) - undefinedundefined"number" (for maths) - undefinedundefined"default" (few operators)undefinedundefined

    undefinedundefined

    The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the undefinedundefined"default" hint. Usually for built-in objects undefinedundefined"default" hint is handled the same way as undefinedundefined"number", so in practice the last two are often merged together.undefinedundefined

    undefinedundefined

    The conversion algorithm is:

    undefinedundefined
      undefinedundefined
    1. Call undefinedundefinedobj[Symbol.toPrimitive](hint) if the method exists,undefinedundefined
    2. undefinedundefined
    3. Otherwise if hint is undefinedundefined"string"undefinedundefined
        undefinedundefined
      • try undefinedundefinedobj.toString() and undefinedundefinedobj.valueOf(), whatever exists.undefinedundefined
      • undefinedundefined
      undefinedundefined
    4. undefinedundefined
    5. Otherwise if hint is undefinedundefined"number" or undefinedundefined"default"undefinedundefined
        undefinedundefined
      • try undefinedundefinedobj.valueOf() and undefinedundefinedobj.toString(), whatever exists.undefinedundefined
      • undefinedundefined
      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    In practice, it's often enough to implement only undefinedundefinedobj.toString() as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes.undefinedundefined

    undefinedundefined

    Methods of primitives

    undefinedundefined

    JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer).

    undefinedundefined

    Let's look at the key distinctions between primitives and objects.

    undefinedundefined

    A primitive

    undefinedundefined
      undefinedundefined
    • Is a value of a primitive type.
    • undefinedundefined
    • There are 7 primitive types: undefinedundefinedstring, undefinedundefinednumber, undefinedundefinedbigint, undefinedundefinedboolean, undefinedundefinedsymbol, undefinedundefinednull and undefinedundefinedundefined.undefinedundefined
    • undefinedundefined
    undefinedundefined

    An object

    undefinedundefined
      undefinedundefined
    • Is capable of storing multiple values as properties.
    • undefinedundefined
    • Can be created with undefinedundefined{}, for instance: undefinedundefined{name: "John", age: 30}. There are other kinds of objects in JavaScript: functions, for example, are objects.undefinedundefined
    • undefinedundefined
    undefinedundefined

    One of the best things about objects is that we can store a function as one of its properties.

    undefinedundefined

    run let john = { name: "John", sayHi: function() { alert("Hi buddy!"); } };

    undefinedundefined

    john.sayHi(); // Hi buddy!

    undefinedundefined

    So here we've made an object undefinedundefinedjohn with the method undefinedundefinedsayHi.undefinedundefined

    undefinedundefined

    Many built-in objects already exist, such as those that work with dates, errors, HTML elements, etc. They have different properties and methods.

    undefinedundefined

    But, these features come with a cost!

    undefinedundefined

    Objects are "heavier" than primitives. They require additional resources to support the internal machinery.

    undefinedundefined

    A primitive as an object

    undefinedundefined

    Here's the paradox faced by the creator of JavaScript:

    undefinedundefined
      undefinedundefined
    • There are many things one would want to do with a primitive like a string or a number. It would be great to access them as methods.
    • undefinedundefined
    • Primitives must be as fast and lightweight as possible.
    • undefinedundefined
    undefinedundefined

    The solution looks a little bit awkward, but here it is:

    undefinedundefined
      undefinedundefined
    1. Primitives are still primitive. A single value, as desired.
    2. undefinedundefined
    3. The language allows access to methods and properties of strings, numbers, booleans and symbols.
    4. undefinedundefined
    5. In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed.
    6. undefinedundefined
    undefinedundefined

    The "object wrappers" are different for each primitive type and are called: undefinedundefinedString, undefinedundefinedNumber, undefinedundefinedBoolean and undefinedundefinedSymbol. Thus, they provide different sets of methods.undefinedundefined

    undefinedundefined

    For instance, there exists a string method undefinedundefinedstr.toUpperCase() that returns a capitalized undefinedundefinedstr.undefinedundefined

    undefinedundefined

    Here's how it works:

    undefinedundefined

    run let str = "Hello";

    undefinedundefined

    alert( str.toUpperCase() ); // HELLO

    undefinedundefined

    Simple, right? Here's what actually happens in undefinedundefinedstr.toUpperCase():undefinedundefined

    undefinedundefined
      undefinedundefined
    1. The string undefinedundefinedstr is a primitive. So in the moment of accessing its property, a special object is created that knows the value of the string, and has useful methods, like undefinedundefinedtoUpperCase().undefinedundefined
    2. undefinedundefined
    3. That method runs and returns a new string (shown by undefinedundefinedalert).undefinedundefined
    4. undefinedundefined
    5. The special object is destroyed, leaving the primitive undefinedundefinedstr alone.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    So primitives can provide methods, but they still remain lightweight.

    undefinedundefined

    The JavaScript engine highly optimizes this process. It may even skip the creation of the extra object at all. But it must still adhere to the specification and behave as if it creates one.

    undefinedundefined

    A number has methods of its own, for instance, undefinedundefinedtoFixed(n) rounds the number to the given precision:undefinedundefined

    undefinedundefined

    run let n = 1.23456;

    undefinedundefined

    alert( n.toFixed(2) ); // 1.23

    undefinedundefined

    We'll see more specific methods in chapters undefinedundefinedinfo:number and undefinedundefinedinfo:string.undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="ConstructorsString/Number/Booleanundefinedundefinedare for internal use only" Some languages like Java allow us to explicitly create "wrapper objects" for primitives using a syntax likenew Number(1)undefinedundefinedornew Boolean(false)`.undefinedundefined

    undefinedundefined

    In JavaScript, that's also possible for historical reasons, but highly undefinedundefinedunrecommended. Things will go crazy in several places.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run alert( typeof 0 ); // "number"

    undefinedundefined

    alert( typeof new Number(0) ); // "object"!

    undefinedundefined

    Objects are always truthy in undefinedundefinedif, so here the alert will show up:undefinedundefined

    undefinedundefined

    run let zero = new Number(0);

    undefinedundefined

    if (zero) { // zero is true, because it's an object alert( "zero is truthy!?!" ); }

    undefinedundefined

    On the other hand, using the same functions undefinedundefinedString/Number/Boolean without undefinedundefinednew is a totally sane and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive).undefinedundefined

    undefinedundefined

    For example, this is entirely valid:

    undefinedundefinedundefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="null/undefined have no methods" The special primitivesnullundefinedundefinedandundefined` are exceptions. They have no corresponding "wrapper objects" and provide no methods. In a sense, they are "the most primitive".undefinedundefined

    undefinedundefined

    An attempt to access a property of such value would give the error:

    undefinedundefined

    run alert(null.test); // error

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Primitives except undefinedundefinednull and undefinedundefinedundefined provide many helpful methods. We will study those in the upcoming chapters.undefinedundefined
    • undefinedundefined
    • Formally, these methods work via temporary objects, but JavaScript engines are well tuned to optimize that internally, so they are not expensive to call.
    • undefinedundefined
    undefinedundefined

    Numbers

    undefinedundefined

    In modern JavaScript, there are two types of numbers:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      Regular numbers in JavaScript are stored in 64-bit format undefinedundefinedIEEE-754, also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter.undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't exceed undefinedundefined2undefinedundefined53undefinedundefined or be less than undefinedundefined-2undefinedundefined53undefinedundefined. As bigints are used in few special areas, we devote them a special chapter undefinedundefinedinfo:bigint.undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    So here we'll talk about regular numbers. Let's expand our knowledge of them.

    undefinedundefined

    More ways to write a number

    undefinedundefined

    Imagine we need to write 1 billion. The obvious way is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet billion undefinedundefined=undefinedundefined1000000000undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    We also can use underscore undefinedundefined_ as the separator:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet billion undefinedundefined=undefinedundefined1_000_000_000undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Here the underscore undefinedundefined_ plays the role of the "syntactic sugar", it makes the number more readable. The JavaScript engine simply ignores undefinedundefined_ between digits, so it's exactly the same one billion as above.undefinedundefined

    undefinedundefined

    In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like undefinedundefined"1bn" for a billion or undefinedundefined"7.3bn" for 7 billion 300 million. The same is true for most large numbers.undefinedundefined

    undefinedundefined

    In JavaScript, we can shorten a number by appending the letter undefinedundefined"e" to it and specifying the zeroes count:undefinedundefined

    undefinedundefined

    run let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes

    undefinedundefined

    alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)

    undefinedundefined

    In other words, undefinedundefinede multiplies the number by undefinedundefined1 with the given zeroes count.undefinedundefined

    undefinedundefinedundefinedundefined

    Now let's write something very small. Say, 1 microsecond (one millionth of a second):

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet ms undefinedundefined=undefinedundefined0.000001undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Just like before, using undefinedundefined"e" can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet ms undefinedundefined=undefinedundefined1e-6undefinedundefined;undefinedundefined// six zeroes to the left from 1undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    If we count the zeroes in undefinedundefined0.000001, there are 6 of them. So naturally it's undefinedundefined1e-6.undefinedundefined

    undefinedundefined

    In other words, a negative number after undefinedundefined"e" means a division by 1 with the given number of zeroes:undefinedundefined

    undefinedundefinedundefinedundefined

    Hex, binary and octal numbers

    undefinedundefined

    undefinedundefinedHexadecimal numbers are widely used in JavaScript to represent colors, encode characters, and for many other things. So naturally, there exists a shorter way to write them: undefinedundefined0x and then the number.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run alert( 0xff ); // 255 alert( 0xFF ); // 255 (the same, case doesn't matter)undefinedundefined

    undefinedundefined

    Binary and octal numeral systems are rarely used, but also supported using the undefinedundefined0b and undefinedundefined0o prefixes:undefinedundefined

    undefinedundefined

    run let a = 0b11111111; // binary form of 255 let b = 0o377; // octal form of 255

    undefinedundefined

    alert( a == b ); // true, the same number 255 at both sides

    undefinedundefined

    There are only 3 numeral systems with such support. For other numeral systems, we should use the function undefinedundefinedparseInt (which we will see later in this chapter).undefinedundefined

    undefinedundefined

    toString(base)

    undefinedundefined

    The method undefinedundefinednum.toString(base) returns a string representation of undefinedundefinednum in the numeral system with the given undefinedundefinedbase.undefinedundefined

    undefinedundefined

    For example: run let num = 255;

    undefinedundefined

    alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111

    undefinedundefined

    The undefinedundefinedbase can vary from undefinedundefined2 to undefinedundefined36. By default it's undefinedundefined10.undefinedundefined

    undefinedundefined

    Common use cases for this are:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedbase=16 is used for hex colors, character encodings etc, digits can be undefinedundefined0..9 or undefinedundefinedA..F.undefinedundefined
    • undefinedundefined
    • undefinedundefinedbase=2 is mostly for debugging bitwise operations, digits can be undefinedundefined0 or undefinedundefined1.undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedbase=36 is the maximum, digits can be undefinedundefined0..9 or undefinedundefinedA..Z. The whole latin alphabet is used to represent a number. A funny, but useful case for undefinedundefined36 is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base undefinedundefined36:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run alert( 123456..toString(36) ); // 2n9cundefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    ``undefinedundefinedwarn header="Two dots to call a method" Please note that two dots in123456..toString(36)undefinedundefinedis not a typo. If we want to call a method directly on a number, liketoStringundefinedundefinedin the example above, then we need to place two dots..` after it.undefinedundefined

    undefinedundefined

    If we placed a single dot: undefinedundefined123456.toString(36), then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method.undefinedundefined

    undefinedundefined

    Also could write undefinedundefined(123456).toString(36). undefinedundefined

    undefinedundefined

    Rounding

    undefinedundefined

    One of the most used operations when working with numbers is rounding.

    undefinedundefined

    There are several built-in functions for rounding:

    undefinedundefined
    undefinedundefined
    undefinedundefinedMath.floorundefinedundefined
    undefinedundefined
    Rounds down: undefinedundefined3.1 becomes undefinedundefined3, and undefinedundefined-1.1 becomes undefinedundefined-2. undefinedundefined
    undefinedundefined
    undefinedundefinedMath.ceilundefinedundefined
    undefinedundefined
    Rounds up: undefinedundefined3.1 becomes undefinedundefined4, and undefinedundefined-1.1 becomes undefinedundefined-1. undefinedundefined
    undefinedundefined
    undefinedundefinedMath.roundundefinedundefined
    undefinedundefined
    Rounds to the nearest integer: undefinedundefined3.1 becomes undefinedundefined3, undefinedundefined3.6 becomes undefinedundefined4, the middle case: undefinedundefined3.5 rounds up to undefinedundefined4 too. undefinedundefined
    undefinedundefined
    undefinedundefinedMath.trunc (not supported by Internet Explorer)undefinedundefined
    undefinedundefined
    Removes anything after the decimal point without rounding: undefinedundefined3.1 becomes undefinedundefined3, undefinedundefined-1.1 becomes undefinedundefined-1. undefinedundefined
    undefinedundefined
    undefinedundefined

    Here's the table to summarize the differences between them:

    undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined
    undefinedundefinedMath.floorundefinedundefined undefinedundefinedMath.ceilundefinedundefined undefinedundefinedMath.roundundefinedundefined undefinedundefinedMath.truncundefinedundefined
    undefinedundefined3.1undefinedundefined undefinedundefined3undefinedundefined undefinedundefined4undefinedundefined undefinedundefined3undefinedundefined undefinedundefined3undefinedundefined
    undefinedundefined3.6undefinedundefined undefinedundefined3undefinedundefined undefinedundefined4undefinedundefined undefinedundefined4undefinedundefined undefinedundefined3undefinedundefined
    undefinedundefined-1.1undefinedundefined undefinedundefined-2undefinedundefined undefinedundefined-1undefinedundefined undefinedundefined-1undefinedundefined undefinedundefined-1undefinedundefined
    undefinedundefined-1.6undefinedundefined undefinedundefined-2undefinedundefined undefinedundefined-1undefinedundefined undefinedundefined-2undefinedundefined undefinedundefined-1undefinedundefined
    undefinedundefined

    These functions cover all of the possible ways to deal with the decimal part of a number. But what if we'd like to round the number to undefinedundefinedn-th digit after the decimal?undefinedundefined

    undefinedundefined

    For instance, we have undefinedundefined1.2345 and want to round it to 2 digits, getting only undefinedundefined1.23.undefinedundefined

    undefinedundefined

    There are two ways to do so:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      Multiply-and-divide.

      undefinedundefined

      For example, to round the number to the 2nd digit after the decimal, we can multiply the number by undefinedundefined100 (or a bigger power of 10), call the rounding function and then divide it back. run let num = 1.23456;undefinedundefined

      undefinedundefined

      alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      The method undefinedundefinedtoFixed(n) rounds the number to undefinedundefinedn digits after the point and returns a string representation of the result.undefinedundefined

      undefinedundefined

      undefinedundefinedjs run let num = 12.34; alert( num.toFixed(1) ); // "12.3"undefinedundefined

      undefinedundefined

      This rounds up or down to the nearest value, similar to undefinedundefinedMath.round:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run let num = 12.36; alert( num.toFixed(1) ); // "12.4"undefinedundefined

      undefinedundefined

      Please note that result of undefinedundefinedtoFixed is a string. If the decimal part is shorter than required, zeroes are appended to the end:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run let num = 12.34; alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digitsundefinedundefined

      undefinedundefined

      We can convert it to a number using the unary plus or a undefinedundefinedNumber() call: undefinedundefined+num.toFixed(5).undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Imprecise calculations

    undefinedundefined

    Internally, a number is represented in 64-bit format undefinedundefinedIEEE-754, so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.undefinedundefined

    undefinedundefined

    If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity:

    undefinedundefined

    undefinedundefinedjs run alert( 1e500 ); // Infinityundefinedundefined

    undefinedundefined

    What may be a little less obvious, but happens quite often, is the loss of precision.

    undefinedundefined

    Consider this (falsy!) test:

    undefinedundefined

    undefinedundefinedjs run alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!*undefinedundefined

    undefinedundefined

    That's right, if we check whether the sum of undefinedundefined0.1 and undefinedundefined0.2 is undefinedundefined0.3, we get undefinedundefinedfalse.undefinedundefined

    undefinedundefined

    Strange! What is it then if not undefinedundefined0.3?undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( 0.1 + 0.2 ); // 0.30000000000000004undefinedundefined

    undefinedundefined

    Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts undefinedundefined$0.10 and undefinedundefined$0.20 goods into their cart. The order total will be undefinedundefined$0.30000000000000004. That would surprise anyone.undefinedundefined

    undefinedundefined

    But why does this happen?

    undefinedundefined

    A number is stored in memory in its binary form, a sequence of bits - ones and zeroes. But fractions like undefinedundefined0.1, undefinedundefined0.2 that look simple in the decimal numeric system are actually unending fractions in their binary form.undefinedundefined

    undefinedundefined

    In other words, what is undefinedundefined0.1? It is one divided by ten undefinedundefined1/10, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third: undefinedundefined1/3. It becomes an endless fraction undefinedundefined0.33333(3).undefinedundefined

    undefinedundefined

    So, division by powers undefinedundefined10 is guaranteed to work well in the decimal system, but division by undefinedundefined3 is not. For the same reason, in the binary numeral system, the division by powers of undefinedundefined2 is guaranteed to work, but undefinedundefined1/10 becomes an endless binary fraction.undefinedundefined

    undefinedundefined

    There's just no way to store undefinedundefinedexactly 0.1 or undefinedundefinedexactly 0.2 using the binary system, just like there is no way to store one-third as a decimal fraction.undefinedundefined

    undefinedundefined

    The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", but it exists.

    undefinedundefined

    We can see this in action: undefinedundefinedjs run alert( 0.1.toFixed(20) ); // 0.10000000000000000555undefinedundefined

    undefinedundefined

    And when we sum two numbers, their "precision losses" add up.

    undefinedundefined

    That's why undefinedundefined0.1 + 0.2 is not exactly undefinedundefined0.3.undefinedundefined

    undefinedundefined

    smart header="Not only JavaScript" The same issue exists in many other programming languages.

    undefinedundefined

    PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format.

    undefinedundefined

    Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method undefinedundefinedtoFixed(n):undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let sum = 0.1 + 0.2; alert( sum.toFixed(2) ); // 0.30undefinedundefined

    undefinedundefined

    Please note that undefinedundefinedtoFixed always returns a string. It ensures that it has 2 digits after the decimal point. That's actually convenient if we have an e-shopping and need to show undefinedundefined$0.30. For other cases, we can use the unary plus to coerce it into a number:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let sum = 0.1 + 0.2; alert( +sum.toFixed(2) ); // 0.3undefinedundefined

    undefinedundefined

    We also can temporarily multiply the numbers by 100 (or a bigger number) to turn them into integers, do the maths, and then divide back. Then, as we're doing maths with integers, the error somewhat decreases, but we still get it on division:

    undefinedundefined

    undefinedundefinedjs run alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001undefinedundefined

    undefinedundefined

    So, multiply/divide approach reduces the error, but doesn't remove it totally.

    undefinedundefined

    Sometimes we could try to evade fractions at all. Like if we're dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut "tails" when needed.

    undefinedundefined

    smart header="The funny thing" Try running this:

    undefinedundefined

    undefinedundefinedjs run // Hello! I'm a self-increasing number! alert( 9999999999999999 ); // shows 10000000000000000undefinedundefined

    undefinedundefined

    This suffers from the same issue: a loss of precision. There are 64 bits for the number, 52 of them can be used to store digits, but that's not enough. So the least significant digits disappear.

    undefinedundefined

    JavaScript doesn't trigger an error in such events. It does its best to fit the number into the desired format, but unfortunately, this format is not big enough.

    undefinedundefined

    ``undefinedundefinedsmart header="Two zeroes" Another funny consequence of the internal representation of numbers is the existence of two zeroes:0undefinedundefinedand-0`.undefinedundefined

    undefinedundefined

    That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero.

    undefinedundefined

    In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.

    undefinedundefined

    Tests: isFinite and isNaN

    undefinedundefined

    Remember these two special numeric values?

    undefinedundefined
      undefinedundefined
    • undefinedundefinedInfinity (and undefinedundefined-Infinity) is a special numeric value that is greater (less) than anything.undefinedundefined
    • undefinedundefined
    • undefinedundefinedNaN represents an error.undefinedundefined
    • undefinedundefined
    undefinedundefined

    They belong to the type undefinedundefinednumber, but are not "normal" numbers, so there are special functions to check for them:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      undefinedundefinedisNaN(value) converts its argument to a number and then tests it for being undefinedundefinedNaN:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run alert( isNaN(NaN) ); // true alert( isNaN("str") ); // trueundefinedundefined

      undefinedundefined

      But do we need this function? Can't we just use the comparison undefinedundefined=== NaN? Sorry, but the answer is no. The value undefinedundefinedNaN is unique in that it does not equal anything, including itself:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run alert( NaN === NaN ); // falseundefinedundefined

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedisFinite(value) converts its argument to a number and returns undefinedundefinedtrue if it's a regular number, not undefinedundefinedNaN/Infinity/-Infinity:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run alert( isFinite("15") ); // true alert( isFinite("str") ); // false, because a special value: NaN alert( isFinite(Infinity) ); // false, because a special value: Infinityundefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Sometimes undefinedundefinedisFinite is used to validate whether a string value is a regular number:undefinedundefined

    undefinedundefined

    run let num = +prompt("Enter a number", '');

    undefinedundefined

    // will be true unless you enter Infinity, -Infinity or not a number alert( isFinite(num) );

    undefinedundefined

    Please note that an empty or a space-only string is treated as undefinedundefined0 in all numeric functions including undefinedundefinedisFinite.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Compare withObject.is`"undefinedundefined

    undefinedundefined

    There is a special built-in method undefinedundefinedundefinedundefinedObject.isundefinedundefined that compares values like undefinedundefined===, but is more reliable for two edge cases:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. It works with undefinedundefinedNaN: undefinedundefinedObject.is(NaN, NaN) === true, that's a good thing.undefinedundefined
    2. undefinedundefined
    3. Values undefinedundefined0 and undefinedundefined-0 are different: undefinedundefinedObject.is(0, -0) === false, technically that's true, because internally the number has a sign bit that may be different even if all other bits are zeroes.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    In all other cases, undefinedundefinedObject.is(a, b) is the same as undefinedundefineda === b.undefinedundefined

    undefinedundefined

    This way of comparison is often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses undefinedundefinedObject.is (internally called undefinedundefinedSameValue). undefinedundefined

    undefinedundefined

    parseInt and parseFloat

    undefinedundefined

    Numeric conversion using a plus undefinedundefined+ or undefinedundefinedNumber() is strict. If a value is not exactly a number, it fails:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( +"100px" ); // NaNundefinedundefined

    undefinedundefined

    The sole exception is spaces at the beginning or at the end of the string, as they are ignored.

    undefinedundefined

    But in real life we often have values in units, like undefinedundefined"100px" or undefinedundefined"12pt" in CSS. Also in many countries the currency symbol goes after the amount, so we have undefinedundefined"19€" and would like to extract a numeric value out of that.undefinedundefined

    undefinedundefined

    That's what undefinedundefinedparseInt and undefinedundefinedparseFloat are for.undefinedundefined

    undefinedundefined

    They "read" a number from a string until they can't. In case of an error, the gathered number is returned. The function undefinedundefinedparseInt returns an integer, whilst undefinedundefinedparseFloat will return a floating-point number:undefinedundefined

    undefinedundefined

    run alert( parseInt(‘100px') ); // 100 alert( parseFloat(‘12.5em') ); // 12.5

    undefinedundefined

    alert( parseInt(‘12.3') ); // 12, only the integer part is returned alert( parseFloat(‘12.3.4') ); // 12.3, the second point stops the reading

    undefinedundefined

    There are situations when undefinedundefinedparseInt/parseFloat will return undefinedundefinedNaN. It happens when no digits could be read:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( parseInt('a123') ); // NaN, the first symbol stops the processundefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="The second argument ofparseInt(str, radix)undefinedundefined" TheparseInt()undefinedundefinedfunction has an optional second parameter. It specifies the base of the numeral system, soparseInt` can also parse strings of hex numbers, binary numbers and so on:undefinedundefined

    undefinedundefined

    run alert( parseInt(‘0xff', 16) ); // 255 alert( parseInt(‘ff', 16) ); // 255, without 0x also works

    undefinedundefined

    alert( parseInt(‘2n9c', 36) ); // 123456

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    Other math functions

    undefinedundefined

    JavaScript has a built-in undefinedundefinedMath object which contains a small library of mathematical functions and constants.undefinedundefined

    undefinedundefined

    A few examples:

    undefinedundefined
    undefinedundefined
    undefinedundefinedMath.random()undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns a random number from 0 to 1 (not including 1).

    undefinedundefined

    undefinedundefinedjs run alert( Math.random() ); // 0.1234567894322 alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... (any random numbers)undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedMath.max(a, b, c...) / undefinedundefinedMath.min(a, b, c...)undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns the greatest/smallest from the arbitrary number of arguments.

    undefinedundefined

    undefinedundefinedjs run alert( Math.max(3, 5, -10, 0, 1) ); // 5 alert( Math.min(1, 2) ); // 1undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedMath.pow(n, power)undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns undefinedundefinedn raised to the given power.undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( Math.pow(2, 10) ); // 2 in power 10 = 1024undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    There are more functions and constants in undefinedundefinedMath object, including trigonometry, which you can find in the undefinedundefineddocs for the Math object.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    To write numbers with many zeroes:

    undefinedundefined
      undefinedundefined
    • Append undefinedundefined"e" with the zeroes count to the number. Like: undefinedundefined123e6 is the same as undefinedundefined123 with 6 zeroes undefinedundefined123000000.undefinedundefined
    • undefinedundefined
    • A negative number after undefinedundefined"e" causes the number to be divided by 1 with given zeroes. E.g. undefinedundefined123e-6 means undefinedundefined0.000123 (undefinedundefined123 millionths).undefinedundefined
    • undefinedundefined
    undefinedundefined

    For different numeral systems:

    undefinedundefined
      undefinedundefined
    • Can write numbers directly in hex (undefinedundefined0x), octal (undefinedundefined0o) and binary (undefinedundefined0b) systems.undefinedundefined
    • undefinedundefined
    • undefinedundefinedparseInt(str, base) parses the string undefinedundefinedstr into an integer in numeral system with given undefinedundefinedbase, undefinedundefined2 ≤ base ≤ 36.undefinedundefined
    • undefinedundefined
    • undefinedundefinednum.toString(base) converts a number to a string in the numeral system with the given undefinedundefinedbase.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For converting values like undefinedundefined12pt and undefinedundefined100px to a number:undefinedundefined

    undefinedundefined
      undefinedundefined
    • Use undefinedundefinedparseInt/parseFloat for the "soft" conversion, which reads a number from a string and then returns the value they could read before the error.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For fractions:

    undefinedundefined
      undefinedundefined
    • Round using undefinedundefinedMath.floor, undefinedundefinedMath.ceil, undefinedundefinedMath.trunc, undefinedundefinedMath.round or undefinedundefinednum.toFixed(precision).undefinedundefined
    • undefinedundefined
    • Make sure to remember there's a loss of precision when working with fractions.
    • undefinedundefined
    undefinedundefined

    More mathematical functions:

    undefinedundefined
      undefinedundefined
    • See the undefinedundefinedMath object when you need them. The library is very small, but can cover basic needs.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Strings

    undefinedundefined

    In JavaScript, the textual data is stored as strings. There is no separate type for a single character.

    undefinedundefined

    The internal format for strings is always undefinedundefinedUTF-16, it is not tied to the page encoding.undefinedundefined

    undefinedundefined

    Quotes

    undefinedundefined

    Let's recall the kinds of quotes.

    undefinedundefined

    Strings can be enclosed within either single quotes, double quotes or backticks:

    undefinedundefinedundefinedundefined

    Single and double quotes are essentially the same. Backticks, however, allow us to embed any expression into the string, by wrapping it in undefinedundefined${…}:undefinedundefined

    undefinedundefined

    run function sum(a, b) { return a + b; }

    undefinedundefined

    alert(undefinedundefined1 + 2 = ${sum(1, 2)}.); // 1 + 2 = 3. undefinedundefined

    undefinedundefined

    Another advantage of using backticks is that they allow a string to span multiple lines:

    undefinedundefined

    ``undefinedundefinedjs run let guestList =Guests: * John * Pete * Mary `;undefinedundefined

    undefinedundefined

    alert(guestList); // a list of guests, multiple lines

    undefinedundefined

    Looks natural, right? But single or double quotes do not work this way.

    undefinedundefined

    If we use them and try to use multiple lines, there'll be an error:

    undefinedundefined

    undefinedundefinedjs run let guestList = "Guests: // Error: Unexpected token ILLEGAL * John";undefinedundefined

    undefinedundefined

    Single and double quotes come from ancient times of language creation when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile.

    undefinedundefined

    Backticks also allow us to specify a "template function" before the first backtick. The syntax is: undefinedundefinedfunc`string`. The function undefinedundefinedfunc is called automatically, receives the string and embedded expressions and can process them. This is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the undefinedundefinedmanual.undefinedundefined

    undefinedundefined

    Special characters

    undefinedundefined

    It is still possible to create multiline strings with single and double quotes by using a so-called "newline character", written as undefinedundefined\n, which denotes a line break:undefinedundefined

    undefinedundefined

    run let guestList = "Guests:JohnPeteMary";

    undefinedundefined

    alert(guestList); // a multiline list of guests

    undefinedundefined

    For example, these two lines are equal, just written differently:

    undefinedundefined

    run let str1 = "Hello"; // two lines using a "newline symbol"

    undefinedundefined

    // two lines using a normal newline and backticks let str2 = undefinedundefinedHello World;undefinedundefined

    undefinedundefined

    alert(str1 == str2); // true

    undefinedundefined

    There are other, less common "special" characters.

    undefinedundefined

    Here's the full list:

    undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined
    CharacterDescription
    undefinedundefined\nundefinedundefinedNew line
    undefinedundefined\rundefinedundefinedCarriage return: not used alone. Windows text files use a combination of two characters undefinedundefined\r\n to represent a line break.undefinedundefined
    undefinedundefined\', undefinedundefined\"undefinedundefinedQuotes
    undefinedundefined\\undefinedundefinedBackslash
    undefinedundefined\tundefinedundefinedTab
    undefinedundefined\b, undefinedundefined\f, undefinedundefined\vundefinedundefinedBackspace, Form Feed, Vertical Tab - kept for compatibility, not used nowadays.
    undefinedundefined\xXXundefinedundefinedUnicode character with the given hexadecimal Unicode undefinedundefinedXX, e.g. undefinedundefined'\x7A' is the same as undefinedundefined'z'.undefinedundefined
    undefinedundefined\uXXXXundefinedundefined A Unicode symbol with the hex code undefinedundefinedXXXX in UTF-16 encoding, for instance undefinedundefined\u00A9 - is a Unicode for the copyright symbol undefinedundefined©. It must be exactly 4 hex digits.undefinedundefined
    undefinedundefined\u{X…XXXXXX} (1 to 6 hex characters)undefinedundefinedA Unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two Unicode symbols, taking 4 bytes. This way we can insert long codes.
    undefinedundefined

    Examples with Unicode:

    undefinedundefined

    undefinedundefinedjs run alert( "\u00A9" ); // © alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long Unicode) alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode)undefinedundefined

    undefinedundefined

    All special characters start with a backslash character undefinedundefined\. It is also called an "escape character".undefinedundefined

    undefinedundefined

    We might also use it if we wanted to insert a quote into the string.

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus!undefinedundefined

    undefinedundefined

    As you can see, we have to prepend the inner quote by the backslash undefinedundefined\', because otherwise it would indicate the string end.undefinedundefined

    undefinedundefined

    Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead:

    undefinedundefined

    undefinedundefinedjs run alert( `I'm the Walrus!` ); // I'm the Walrus!undefinedundefined

    undefinedundefined

    Note that the backslash undefinedundefined\ serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no undefinedundefined\. You can clearly see that in undefinedundefinedalert from the examples above.undefinedundefined

    undefinedundefined

    But what if we need to show an actual backslash undefinedundefined\ within the string?undefinedundefined

    undefinedundefined

    That's possible, but we need to double it like undefinedundefined\\:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( `The backslash: \\` ); // The backslash: \undefinedundefined

    undefinedundefined

    String length

    undefinedundefined

    The undefinedundefinedlength property has the string length:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( `My\n`.length ); // 3undefinedundefined

    undefinedundefined

    Note that undefinedundefined\n is a single "special" character, so the length is indeed undefinedundefined3.undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="lengthundefinedundefinedis a property" People with a background in some other languages sometimes mistype by callingstr.length()undefinedundefinedinstead of juststr.length`. That doesn't work.undefinedundefined

    undefinedundefined

    Please note that undefinedundefinedstr.length is a numeric property, not a function. There is no need to add parenthesis after it. undefinedundefined

    undefinedundefined

    Accessing characters

    undefinedundefined

    To get a character at position undefinedundefinedpos, use square brackets undefinedundefined[pos] or call the method undefinedundefinedstr.charAt(pos). The first character starts from the zero position:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let str =Hello`;undefinedundefined

    undefinedundefined

    // the first character alert( str[0] ); // H alert( str.charAt(0) ); // H

    undefinedundefined

    // the last character alert( str[str.length - 1] ); // o

    undefinedundefined

    The square brackets are a modern way of getting a character, while undefinedundefinedcharAt exists mostly for historical reasons.undefinedundefined

    undefinedundefined

    The only difference between them is that if no character is found, undefinedundefined[] returns undefinedundefinedundefined, and undefinedundefinedcharAt returns an empty string:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let str =Hello`;undefinedundefined

    undefinedundefined

    alert( str[1000] ); // undefined alert( str.charAt(1000) ); // '' (an empty string)

    undefinedundefined

    We can also iterate over characters using undefinedundefinedfor..of:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run for (let char of "Hello") { alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc) }undefinedundefined

    undefinedundefined

    Strings are immutable

    undefinedundefined

    Strings can't be changed in JavaScript. It is impossible to change a character.

    undefinedundefined

    Let's try it to show that it doesn't work:

    undefinedundefined

    run let str = ‘Hi';

    undefinedundefined

    str[0] = ‘h'; // error alert( str[0] ); // doesn't work

    undefinedundefined

    The usual workaround is to create a whole new string and assign it to undefinedundefinedstr instead of the old one.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let str = ‘Hi';

    undefinedundefined

    str = ‘h' + str[1]; // replace the string

    undefinedundefined

    alert( str ); // hi

    undefinedundefined

    In the following sections we'll see more examples of this.

    undefinedundefined

    Changing the case

    undefinedundefined

    Methods undefinedundefinedtoLowerCase() and undefinedundefinedtoUpperCase() change the case:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( 'Interface'.toUpperCase() ); // INTERFACE alert( 'Interface'.toLowerCase() ); // interfaceundefinedundefined

    undefinedundefined

    Or, if we want a single character lowercased:

    undefinedundefinedundefinedundefined

    Searching for a substring

    undefinedundefined

    There are multiple ways to look for a substring within a string.

    undefinedundefined

    str.indexOf

    undefinedundefined

    The first method is undefinedundefinedstr.indexOf(substr, pos).undefinedundefined

    undefinedundefined

    It looks for the undefinedundefinedsubstr in undefinedundefinedstr, starting from the given position undefinedundefinedpos, and returns the position where the match was found or undefinedundefined-1 if nothing can be found.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let str = ‘Widget with id';

    undefinedundefined

    alert( str.indexOf(‘Widget') ); // 0, because ‘Widget' is found at the beginning alert( str.indexOf(‘widget') ); // -1, not found, the search is case-sensitive

    undefinedundefined

    alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id)

    undefinedundefined

    The optional second parameter allows us to start searching from a given position.

    undefinedundefined

    For instance, the first occurrence of undefinedundefined"id" is at position undefinedundefined1. To look for the next occurrence, let's start the search from position undefinedundefined2:undefinedundefined

    undefinedundefined

    run let str = ‘Widget with id';

    undefinedundefined

    alert( str.indexOf(‘id', 2) ) // 12

    undefinedundefined

    If we're interested in all occurrences, we can run undefinedundefinedindexOf in a loop. Every new call is made with the position after the previous match:undefinedundefined

    undefinedundefined

    run let str = ‘As sly as a fox, as strong as an ox';

    undefinedundefined

    let target = ‘as'; // let's look for it

    undefinedundefined

    let pos = 0; while (true) { let foundPos = str.indexOf(target, pos); if (foundPos == -1) break;

    undefinedundefined

    alert( undefinedundefinedFound at ${foundPos} ); pos = foundPos + 1; // continue the search from the next position } undefinedundefined

    undefinedundefined

    The same algorithm can be layed out shorter:

    undefinedundefined

    run let str = "As sly as a fox, as strong as an ox"; let target = "as";

    undefinedundefined

    undefinedundefined! let pos = -1; while ((pos = str.indexOf(target, pos + 1)) != -1) { alert( pos ); } undefinedundefined/! undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="str.lastIndexOf(substr, position)`" There is also a similar method undefinedundefinedstr.lastIndexOf(substr, position) that searches from the end of a string to its beginning.undefinedundefined

    undefinedundefined

    It would list the occurrences in the reverse order.

    undefinedundefined

    There is a slight inconvenience with undefinedundefinedindexOf in the undefinedundefinedif test. We can't put it in the undefinedundefinedif like this:undefinedundefined

    undefinedundefined

    run let str = "Widget with id";

    undefinedundefined

    if (str.indexOf("Widget")) { alert("We found it"); // doesn't work! }

    undefinedundefined

    The undefinedundefinedalert in the example above doesn't show because undefinedundefinedstr.indexOf("Widget") returns undefinedundefined0 (meaning that it found the match at the starting position). Right, but undefinedundefinedif considers undefinedundefined0 to be undefinedundefinedfalse.undefinedundefined

    undefinedundefined

    So, we should actually check for undefinedundefined-1, like this:undefinedundefined

    undefinedundefined

    run let str = "Widget with id";

    undefinedundefined

    undefinedundefined! if (str.indexOf("Widget") != -1) { undefinedundefined/! alert("We found it"); // works now! } undefinedundefined

    undefinedundefined

    The bitwise NOT trick

    undefinedundefined

    One of the old tricks used here is the undefinedundefinedbitwise NOTundefinedundefined~ operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation.undefinedundefined

    undefinedundefined

    In practice, that means a simple thing: for 32-bit integers undefinedundefined~n equals undefinedundefined-(n+1).undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run alert( ~2 ); // -3, the same as -(2+1) alert( ~1 ); // -2, the same as -(1+1) alert( ~0 ); // -1, the same as -(0+1) *!* alert( ~-1 ); // 0, the same as -(-1+1) */!*undefinedundefined

    undefinedundefined

    As we can see, undefinedundefined~n is zero only if undefinedundefinedn == -1 (that's for any 32-bit signed integer undefinedundefinedn).undefinedundefined

    undefinedundefined

    So, the test undefinedundefinedif ( ~str.indexOf("...") ) is truthy only if the result of undefinedundefinedindexOf is not undefinedundefined-1. In other words, when there is a match.undefinedundefined

    undefinedundefined

    People use it to shorten undefinedundefinedindexOf checks:undefinedundefined

    undefinedundefined

    run let str = "Widget";

    undefinedundefined

    if (~str.indexOf("Widget")) { alert( ‘Found it!''' ); // works }

    undefinedundefined

    It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used in old code, so we should understand it.

    undefinedundefined

    Just remember: undefinedundefinedif (~str.indexOf(...)) reads as "if found".undefinedundefined

    undefinedundefined

    To be precise though, as big numbers are truncated to 32 bits by undefinedundefined~ operator, there exist other numbers that give undefinedundefined0, the smallest is undefinedundefined~4294967295=0. That makes such check correct only if a string is not that long.undefinedundefined

    undefinedundefined

    Right now we can see this trick only in the old code, as modern JavaScript provides undefinedundefined.includes method (see below).undefinedundefined

    undefinedundefined

    includes, startsWith, endsWith

    undefinedundefined

    The more modern method undefinedundefinedstr.includes(substr, pos) returns undefinedundefinedtrue/false depending on whether undefinedundefinedstr contains undefinedundefinedsubstr within.undefinedundefined

    undefinedundefined

    It's the right choice if we need to test for the match, but don't need its position:

    undefinedundefined

    run alert( "Widget with id".includes("Widget") ); // true

    undefinedundefined

    alert( "Hello".includes("Bye") ); // false

    undefinedundefined

    The optional second argument of undefinedundefinedstr.includes is the position to start searching from:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( "Widget".includes("id") ); // true alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id"undefinedundefined

    undefinedundefined

    The methods undefinedundefinedstr.startsWith and undefinedundefinedstr.endsWith do exactly what they say:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid" alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"undefinedundefined

    undefinedundefined

    Getting a substring

    undefinedundefined

    There are 3 methods in JavaScript to get a substring: undefinedundefinedsubstring, undefinedundefinedsubstr and undefinedundefinedslice.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedstr.slice(start [, end])undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns the part of the string from undefinedundefinedstart to (but not including) undefinedundefinedend.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run let str = "stringify"; alert( str.slice(0, 5) ); // 'strin', the substring from 0 to 5 (not including 5) alert( str.slice(0, 1) ); // 's', from 0 to 1, but not including 1, so only character at 0undefinedundefined

    undefinedundefined

    If there is no second argument, then undefinedundefinedslice goes till the end of the string:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let str = "st*!*ringify*/!*"; alert( str.slice(2) ); // 'ringify', from the 2nd position till the endundefinedundefined

    undefinedundefined

    Negative values for undefinedundefinedstart/end are also possible. They mean the position is counted from the string end:undefinedundefined

    undefinedundefined

    run let str = "strinundefinedundefined!gifundefinedundefined/!y";undefinedundefined

    undefinedundefined

    // start at the 4th position from the right, end at the 1st from the right alert( str.slice(-4, -1) ); // ‘gif'

    undefinedundefined
    undefinedundefined
    undefinedundefinedstr.substring(start [, end])undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns the part of the string undefinedundefinedbetweenundefinedundefinedstart and undefinedundefinedend.undefinedundefined

    undefinedundefined

    This is almost the same as undefinedundefinedslice, but it allows undefinedundefinedstart to be greater than undefinedundefinedend.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let str = "stundefinedundefined!ringundefinedundefined/!ify";undefinedundefined

    undefinedundefined

    // these are same for substring alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring"

    undefinedundefined

    // …but not for slice: alert( str.slice(2, 6) ); // "ring" (the same) alert( str.slice(6, 2) ); // "" (an empty string)

    undefinedundefined

    undefinedundefined

    Negative arguments are (unlike slice) not supported, they are treated as undefinedundefined0.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedstr.substr(start [, length])undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns the part of the string from undefinedundefinedstart, with the given undefinedundefinedlength.undefinedundefined

    undefinedundefined

    In contrast with the previous methods, this one allows us to specify the undefinedundefinedlength instead of the ending position:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let str = "st*!*ring*/!*ify"; alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 charactersundefinedundefined

    undefinedundefined

    The first argument may be negative, to count from the end:

    undefinedundefined

    undefinedundefinedjs run let str = "strin*!*gi*/!*fy"; alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 charactersundefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    Let's recap these methods to avoid any confusion:

    undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined
    methodselects… negatives
    undefinedundefinedslice(start, end)undefinedundefined from undefinedundefinedstart to undefinedundefinedend (not including undefinedundefinedend)undefinedundefinedallows negatives
    undefinedundefinedsubstring(start, end)undefinedundefined between undefinedundefinedstart and undefinedundefinedendundefinedundefinednegative values mean undefinedundefined0undefinedundefined
    undefinedundefinedsubstr(start, length)undefinedundefined from undefinedundefinedstart get undefinedundefinedlength charactersundefinedundefined allows negative undefinedundefinedstartundefinedundefined
    undefinedundefined

    ``undefinedundefinedsmart header="Which one to choose?" All of them can do the job. Formally,substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere.undefinedundefined

    undefinedundefined

    Of the other two variants, undefinedundefinedslice is a little bit more flexible, it allows negative arguments and shorter to write. So, it's enough to remember solely undefinedundefinedslice of these three methods. undefinedundefined

    undefinedundefined

    Comparing strings

    undefinedundefined

    As we know from the chapter undefinedundefinedinfo:comparison, strings are compared character-by-character in alphabetical order.undefinedundefined

    undefinedundefined

    Although, there are some oddities.

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      A lowercase letter is always greater than the uppercase:

      undefinedundefined

      undefinedundefinedjs run alert( 'a' > 'Z' ); // trueundefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Letters with diacritical marks are "out of order":

      undefinedundefined

      undefinedundefinedjs run alert( 'Österreich' > 'Zealand' ); // trueundefinedundefined

      undefinedundefined

      This may lead to strange results if we sort these country names. Usually people would expect undefinedundefinedZealand to come after undefinedundefinedÖsterreich in the list.undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    To understand what happens, let's review the internal representation of strings in JavaScript.

    undefinedundefined

    All strings are encoded using undefinedundefinedUTF-16. That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedstr.codePointAt(pos)undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns the code for the character at position undefinedundefinedpos:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // different case letters have different codes alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedString.fromCodePoint(code)undefinedundefined
    undefinedundefined
    undefinedundefined

    Creates a character by its numeric undefinedundefinedcodeundefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( String.fromCodePoint(90) ); // Zundefinedundefined

    undefinedundefined

    We can also add Unicode characters by their codes using undefinedundefined\u followed by the hex code:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // 90 is 5a in hexadecimal system alert( '\u005a' ); // Zundefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    Now let's see the characters with codes undefinedundefined65..220 (the latin alphabet and a little bit extra) by making a string of them:undefinedundefined

    undefinedundefined

    run let str = '';

    undefinedundefined

    for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); // ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ

    undefinedundefined

    See? Capital characters go first, then a few special ones, then lowercase characters, and undefinedundefinedÖ near the end of the output.undefinedundefined

    undefinedundefined

    Now it becomes obvious why undefinedundefineda > Z.undefinedundefined

    undefinedundefined

    The characters are compared by their numeric code. The greater code means that the character is greater. The code for undefinedundefineda (97) is greater than the code for undefinedundefinedZ (90).undefinedundefined

    undefinedundefined
      undefinedundefined
    • All lowercase letters go after uppercase letters because their codes are greater.
    • undefinedundefined
    • Some letters like undefinedundefinedÖ stand apart from the main alphabet. Here, it's code is greater than anything from undefinedundefineda to undefinedundefinedz.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Correct comparisons [#correct-comparisons]

    undefinedundefined

    The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages.

    undefinedundefined

    So, the browser needs to know the language to compare.

    undefinedundefined

    Luckily, all modern browsers (IE10- requires the additional library undefinedundefinedIntl.js) support the internationalization standard undefinedundefinedECMA-402.undefinedundefined

    undefinedundefined

    It provides a special method to compare strings in different languages, following their rules.

    undefinedundefined

    The call undefinedundefinedstr.localeCompare(str2) returns an integer indicating whether undefinedundefinedstr is less, equal or greater than undefinedundefinedstr2 according to the language rules:undefinedundefined

    undefinedundefined
      undefinedundefined
    • Returns a negative number if undefinedundefinedstr is less than undefinedundefinedstr2.undefinedundefined
    • undefinedundefined
    • Returns a positive number if undefinedundefinedstr is greater than undefinedundefinedstr2.undefinedundefined
    • undefinedundefined
    • Returns undefinedundefined0 if they are equivalent.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run alert( 'Österreich'.localeCompare('Zealand') ); // -1undefinedundefined

    undefinedundefined

    This method actually has two additional arguments specified in undefinedundefinedthe documentation, which allows it to specify the language (by default taken from the environment, letter order depends on the language) and setup additional rules like case sensitivity or should undefinedundefined"a" and undefinedundefined"á" be treated as the same etc.undefinedundefined

    undefinedundefined

    Internals, Unicode

    undefinedundefined

    warn header="Advanced knowledge" The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters or other rare symbols.

    undefinedundefined

    You can skip the section if you don't plan to support them.

    undefinedundefined

    Surrogate pairs

    undefinedundefined

    All frequently used characters have 2-byte codes. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation.

    undefinedundefined

    But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair".

    undefinedundefined

    The length of such symbols is undefinedundefined2:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY alert( '𩷶'.length ); // 2, a rare Chinese hieroglyphundefinedundefined

    undefinedundefined

    Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language!

    undefinedundefined

    We actually have a single symbol in each of the strings above, but the undefinedundefinedlength shows a length of undefinedundefined2.undefinedundefined

    undefinedundefined

    undefinedundefinedString.fromCodePoint and undefinedundefinedstr.codePointAt are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only undefinedundefinedString.fromCharCode and undefinedundefinedstr.charCodeAt. These methods are actually the same as undefinedundefinedfromCodePoint/codePointAt, but don't work with surrogate pairs.undefinedundefined

    undefinedundefined

    Getting a symbol can be tricky, because surrogate pairs are treated as two characters:

    undefinedundefined

    undefinedundefinedjs run alert( '𝒳'[0] ); // strange symbols... alert( '𝒳'[1] ); // ...pieces of the surrogate pairundefinedundefined

    undefinedundefined

    Note that pieces of the surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage.

    undefinedundefined

    Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of undefinedundefined0xd800..0xdbff, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval undefinedundefined0xdc00..0xdfff. These intervals are reserved exclusively for surrogate pairs by the standard.undefinedundefined

    undefinedundefined

    In the case above:

    undefinedundefined

    run // charCodeAt is not surrogate-pair aware, so it gives codes for parts

    undefinedundefined

    alert( ‘𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff alert( ‘𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff

    undefinedundefined

    You will find more ways to deal with surrogate pairs later in the chapter undefinedundefinedinfo:iterable. There are probably special libraries for that too, but nothing famous enough to suggest here.undefinedundefined

    undefinedundefined

    Diacritical marks and normalization

    undefinedundefined

    In many languages there are symbols that are composed of the base character with a mark above/under it.

    undefinedundefined

    For instance, the letter undefinedundefineda can be the base character for: undefinedundefinedàáâäãåā. Most common "composite" character have their own code in the UTF-16 table. But not all of them, because there are too many possible combinations.undefinedundefined

    undefinedundefined

    To support arbitrary compositions, UTF-16 allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it.

    undefinedundefined

    For instance, if we have undefinedundefinedS followed by the special "dot above" character (code undefinedundefined\u0307), it is shown as Ṡ.undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( 'S\u0307' ); // Ṡundefinedundefined

    undefinedundefined

    If we need an additional mark above the letter (or below it) - no problem, just add the necessary mark character.

    undefinedundefined

    For instance, if we append a character "dot below" (code undefinedundefined\u0323), then we'll have "S with dots above and below": undefinedundefinedṨ.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefined

    undefinedundefinedjs run alert( 'S\u0307\u0323' ); // Ṩundefinedundefined

    undefinedundefined

    This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions.

    undefinedundefined

    For instance:

    run let s1 = 'S undefinedundefined

    Developer console

    undefinedundefined

    Code is prone to errors. You will quite likely make errors… Oh, what am I talking about? You are undefinedundefinedabsolutely going to make errors, at least if you're a human, not a undefinedundefinedrobot.undefinedundefined

    undefinedundefined

    But in the browser, users don't see errors by default. So, if something goes wrong in the script, we won't see what's broken and can't fix it.

    undefinedundefined

    To see errors and get a lot of other useful information about scripts, "developer tools" have been embedded in browsers.

    undefinedundefined

    Most developers lean towards Chrome or Firefox for development because those browsers have the best developer tools. Other browsers also provide developer tools, sometimes with special features, but are usually playing "catch-up" to Chrome or Firefox. So most developers have a "favorite" browser and switch to others if a problem is browser-specific.

    undefinedundefined

    Developer tools are potent; they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands.

    undefinedundefined

    Google Chrome

    undefinedundefined

    Open the page undefinedundefinedbug.html.undefinedundefined

    undefinedundefined

    There's an error in the JavaScript code on it. It's hidden from a regular visitor's eyes, so let's open developer tools to see it.

    undefinedundefined

    Press undefinedundefinedkey:F12 or, if you're on Mac, then undefinedundefinedkey:Cmd+Opt+J.undefinedundefined

    undefinedundefined

    The developer tools will open on the Console tab by default.

    undefinedundefined

    It looks somewhat like this:

    undefinedundefined
    undefinedundefinedchromeundefinedundefined
    chrome
    undefinedundefined
    undefinedundefined

    The exact look of developer tools depends on your version of Chrome. It changes from time to time but should be similar.

    undefinedundefined
      undefinedundefined
    • Here we can see the red-colored error message. In this case, the script contains an unknown "lalala" command.
    • undefinedundefined
    • On the right, there is a clickable link to the source undefinedundefinedbug.html:12 with the line number where the error has occurred.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Below the error message, there is a blue undefinedundefined> symbol. It marks a "command line" where we can type JavaScript commands. Press undefinedundefinedkey:Enter to run them.undefinedundefined

    undefinedundefined

    Now we can see errors, and that's enough for a start. We'll come back to developer tools later and cover debugging more in-depth in the chapter undefinedundefinedinfo:debugging-chrome.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Multi-line input" Usually, when we put a line of code into the console, and then presskey:Enter`, it executes.undefinedundefined

    undefinedundefined

    To insert multiple lines, press undefinedundefinedkey:Shift+Enter. This way one can enter long fragments of JavaScript code. undefinedundefined

    undefinedundefined

    Firefox, Edge, and others

    undefinedundefined

    Most other browsers use undefinedundefinedkey:F12 to open developer tools.undefinedundefined

    undefinedundefined

    The look & feel of them is quite similar. Once you know how to use one of these tools (you can start with Chrome), you can easily switch to another.

    undefinedundefined

    Safari

    undefinedundefined

    Safari (Mac browser, not supported by Windows/Linux) is a little bit special here. We need to enable the "Develop menu" first.

    undefinedundefined

    Open Preferences and go to the "Advanced" pane. There's a checkbox at the bottom:

    undefinedundefined
    undefinedundefinedsafariundefinedundefined
    safari
    undefinedundefined
    undefinedundefined

    Now undefinedundefinedkey:Cmd+Opt+C can toggle the console. Also, note that the new top menu item named "Develop" has appeared. It has many commands and options.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Developer tools allow us to see errors, run commands, examine variables, and much more.
    • undefinedundefined
    • They can be opened with undefinedundefinedkey:F12 for most browsers on Windows. Chrome for Mac needs undefinedundefinedkey:Cmd+Opt+J, Safari: undefinedundefinedkey:Cmd+Opt+C (need to enable first).undefinedundefined
    • undefinedundefined
    undefinedundefined

    Now we have the environment ready. In the next section, we'll get down to JavaScript.

    undefinedundefined

    Arrays

    undefinedundefined

    Objects allow you to store keyed collections of values. That's fine.

    undefinedundefined

    But quite often we find that we need an undefinedundefinedordered collection, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc.undefinedundefined

    undefinedundefined

    It is not convenient to use an object here, because it provides no methods to manage the order of elements. We can't insert a new property "between" the existing ones. Objects are just not meant for such use.

    undefinedundefined

    There exists a special data structure named undefinedundefinedArray, to store ordered collections.undefinedundefined

    undefinedundefined

    Declaration

    undefinedundefined

    There are two syntaxes for creating an empty array:

    undefinedundefined undefinedundefined

    Almost all the time, the second syntax is used. We can supply initial elements in the brackets:

    undefinedundefinedundefinedundefined

    Array elements are numbered, starting with zero.

    undefinedundefined

    We can get an element by its number in square brackets:

    undefinedundefined

    run let fruits = ["Apple", "Orange", "Plum"];

    undefinedundefined

    alert( fruits[0] ); // Apple alert( fruits[1] ); // Orange alert( fruits[2] ); // Plum

    undefinedundefined

    We can replace an element:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedfruits[undefinedundefined2] undefinedundefined=undefinedundefined'Pear'undefinedundefined;undefinedundefined// now ["Apple", "Orange", "Pear"]undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    …Or add a new one to the array:

    undefinedundefinedundefinedundefined

    The total count of the elements in the array is its undefinedundefinedlength:undefinedundefined

    undefinedundefined

    run let fruits = ["Apple", "Orange", "Plum"];

    undefinedundefined

    alert( fruits.length ); // 3

    undefinedundefined

    We can also use undefinedundefinedalert to show the whole array.undefinedundefined

    undefinedundefined

    run let fruits = ["Apple", "Orange", "Plum"];

    undefinedundefined

    alert( fruits ); // Apple,Orange,Plum

    undefinedundefined

    An array can store elements of any type.

    undefinedundefined

    For instance:

    undefinedundefined

    run no-beautify // mix of values let arr = [ ‘Apple', { name: ‘John' }, true, function() { alert(‘hello'); } ];

    undefinedundefined

    // get the object at index 1 and then show its name alert( arr[1].name ); // John

    undefinedundefined

    // get the function at index 3 and run it arrundefinedundefined3; // hello undefinedundefined

    undefinedundefined

    `undefinedundefinedsmart header="Trailing comma" An array, just like an object, may end with a comma:js let fruits = [ "Apple", "Orange", "Plum"undefinedundefined!,undefinedundefined/!];undefinedundefined

    undefinedundefined
    undefinedundefined
    The "trailing comma" style makes it easier to insert/remove items, because all lines become alike.undefinedundefined
    undefinedundefined

    Methods pop/push, shift/unshift

    undefinedundefined

    A undefinedundefinedqueue is one of the most common uses of an array. In computer science, this means an ordered collection of elements which supports two operations:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedpush appends an element to the end.undefinedundefined
    • undefinedundefined
    • undefinedundefinedshift get an element from the beginning, advancing the queue, so that the 2nd element becomes the 1st.undefinedundefined
    • undefinedundefined
    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Arrays support both operations.

    undefinedundefined

    In practice we need it very often. For example, a queue of messages that need to be shown on-screen.

    undefinedundefined

    There's another use case for arrays - the data structure named undefinedundefinedstack.undefinedundefined

    undefinedundefined

    It supports two operations:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedpush adds an element to the end.undefinedundefined
    • undefinedundefined
    • undefinedundefinedpop takes an element from the end.undefinedundefined
    • undefinedundefined
    undefinedundefined

    So new elements are added or taken always from the "end".

    undefinedundefined

    A stack is usually illustrated as a pack of cards: new cards are added to the top or taken from the top:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).

    undefinedundefined

    Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.

    undefinedundefined

    In computer science the data structure that allows this, is called undefinedundefineddeque.undefinedundefined

    undefinedundefined

    undefinedundefinedMethods that work with the end of the array:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedpopundefinedundefined
    undefinedundefined
    undefinedundefined

    Extracts the last element of the array and returns it:

    undefinedundefined

    run let fruits = ["Apple", "Orange", "Pear"];

    undefinedundefined

    alert( fruits.pop() ); // remove "Pear" and alert it

    undefinedundefined

    alert( fruits ); // Apple, Orange

    undefinedundefined
    undefinedundefined
    undefinedundefinedpushundefinedundefined
    undefinedundefined
    undefinedundefined

    Append the element to the end of the array:

    undefinedundefined

    run let fruits = ["Apple", "Orange"];

    undefinedundefined

    fruits.push("Pear");

    undefinedundefined

    alert( fruits ); // Apple, Orange, Pear

    undefinedundefined

    The call undefinedundefinedfruits.push(...) is equal to undefinedundefinedfruits[fruits.length] = ....undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefinedMethods that work with the beginning of the array:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedshiftundefinedundefined
    undefinedundefined
    undefinedundefined

    Extracts the first element of the array and returns it:

    undefinedundefined

    run let fruits = ["Apple", "Orange", "Pear"];

    undefinedundefined

    alert( fruits.shift() ); // remove Apple and alert it

    undefinedundefined

    alert( fruits ); // Orange, Pear

    undefinedundefined
    undefinedundefined
    undefinedundefinedunshiftundefinedundefined
    undefinedundefined
    undefinedundefined

    Add the element to the beginning of the array:

    undefinedundefined

    run let fruits = ["Orange", "Pear"];

    undefinedundefined

    fruits.unshift(‘Apple');

    undefinedundefined

    alert( fruits ); // Apple, Orange, Pear

    undefinedundefined
    undefinedundefined
    undefinedundefined

    Methods undefinedundefinedpush and undefinedundefinedunshift can add multiple elements at once:undefinedundefined

    undefinedundefined

    run let fruits = ["Apple"];

    undefinedundefined

    fruits.push("Orange", "Peach"); fruits.unshift("Pineapple", "Lemon");

    undefinedundefined

    // ["Pineapple", "Lemon", "Apple", "Orange", "Peach"] alert( fruits );

    undefinedundefined

    Internals

    undefinedundefined

    An array is a special kind of object. The square brackets used to access a property undefinedundefinedarr[0] actually come from the object syntax. That's essentially the same as undefinedundefinedobj[key], where undefinedundefinedarr is the object, while numbers are used as keys.undefinedundefined

    undefinedundefined

    They extend objects providing special methods to work with ordered collections of data and also the undefinedundefinedlength property. But at the core it's still an object.undefinedundefined

    undefinedundefined

    Remember, there are only eight basic data types in JavaScript (see the undefinedundefinedData types chapter for more info). Array is an object and thus behaves like an object.undefinedundefined

    undefinedundefined

    For instance, it is copied by reference:

    undefinedundefined

    run let fruits = ["Banana"]

    undefinedundefined

    let arr = fruits; // copy by reference (two variables reference the same array)

    undefinedundefined

    alert( arr === fruits ); // true

    undefinedundefined

    arr.push("Pear"); // modify the array by reference

    undefinedundefined

    alert( fruits ); // Banana, Pear - 2 items now

    undefinedundefined

    …But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.

    undefinedundefined

    But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.

    undefinedundefined

    For instance, technically we can do this:

    undefinedundefined undefinedundefined

    That's possible, because arrays are objects at their base. We can add any properties to them.

    undefinedundefined

    But the engine will see that we're working with the array as with a regular object. Array-specific optimizations are not suited for such cases and will be turned off, their benefits disappear.

    undefinedundefined

    The ways to misuse an array:

    undefinedundefined
      undefinedundefined
    • Add a non-numeric property like undefinedundefinedarr.test = 5.undefinedundefined
    • undefinedundefined
    • Make holes, like: add undefinedundefinedarr[0] and then undefinedundefinedarr[1000] (and nothing between them).undefinedundefined
    • undefinedundefined
    • Fill the array in the reverse order, like undefinedundefinedarr[1000], undefinedundefinedarr[999] and so on.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Please think of arrays as special structures to work with the undefinedundefinedordered data. They provide special methods for that. Arrays are carefully tuned inside JavaScript engines to work with contiguous ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object undefinedundefined{}.undefinedundefined

    undefinedundefined

    Performance

    undefinedundefined

    Methods undefinedundefinedpush/pop run fast, while undefinedundefinedshift/unshift are slow.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Why is it faster to work with the end of an array than with its beginning? Let's see what happens during the execution:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfruits.undefinedundefinedshift()undefinedundefined;undefinedundefined// take 1 element from the startundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It's not enough to take and remove the element with the number undefinedundefined0. Other elements need to be renumbered as well.undefinedundefined

    undefinedundefined

    The undefinedundefinedshift operation must do 3 things:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Remove the element with the index undefinedundefined0.undefinedundefined
    2. undefinedundefined
    3. Move all elements to the left, renumber them from the index undefinedundefined1 to undefinedundefined0, from undefinedundefined2 to undefinedundefined1 and so on.undefinedundefined
    4. undefinedundefined
    5. Update the undefinedundefinedlength property.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    undefinedundefinedThe more elements in the array, the more time to move them, more in-memory operations.undefinedundefined

    undefinedundefined

    The similar thing happens with undefinedundefinedunshift: to add an element to the beginning of the array, we need first to move existing elements to the right, increasing their indexes.undefinedundefined

    undefinedundefined

    And what's with undefinedundefinedpush/pop? They do not need to move anything. To extract an element from the end, the undefinedundefinedpop method cleans the index and shortens undefinedundefinedlength.undefinedundefined

    undefinedundefined

    The actions for the undefinedundefinedpop operation:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfruits.undefinedundefinedpop()undefinedundefined;undefinedundefined// take 1 element from the endundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    undefinedundefinedThe undefinedundefinedpop method does not need to move anything, because other elements keep their indexes. That's why it's blazingly fast.undefinedundefinedundefinedundefined

    undefinedundefined

    The similar thing with the undefinedundefinedpush method.undefinedundefined

    undefinedundefined

    Loops

    undefinedundefined

    One of the oldest ways to cycle array items is the undefinedundefinedfor loop over indexes:undefinedundefined

    undefinedundefined

    run let arr = ["Apple", "Orange", "Pear"];

    undefinedundefined

    undefinedundefined! for (let i = 0; i < arr.length; i++) { undefinedundefined/! alert( arr[i] ); } undefinedundefined

    undefinedundefined

    But for arrays there is another form of loop, undefinedundefinedfor..of:undefinedundefined

    undefinedundefined

    run let fruits = ["Apple", "Orange", "Plum"];

    undefinedundefined

    // iterates over array elements for (let fruit of fruits) { alert( fruit ); }

    undefinedundefined

    The undefinedundefinedfor..of doesn't give access to the number of the current element, just its value, but in most cases that's enough. And it's shorter.undefinedundefined

    undefinedundefined

    Technically, because arrays are objects, it is also possible to use undefinedundefinedfor..in:undefinedundefined

    undefinedundefined

    run let arr = ["Apple", "Orange", "Pear"];

    undefinedundefined

    undefinedundefined! for (let key in arr) { undefinedundefined/! alert( arr[key] ); // Apple, Orange, Pear } undefinedundefined

    undefinedundefined

    But that's actually a bad idea. There are potential problems with it:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      The loop undefinedundefinedfor..in iterates over undefinedundefinedall properties, not only the numeric ones.undefinedundefined

      undefinedundefined

      There are so-called "array-like" objects in the browser and in other environments, that undefinedundefinedlook like arrays. That is, they have undefinedundefinedlength and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The undefinedundefinedfor..in loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem.undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      The undefinedundefinedfor..in loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks. But still we should be aware of the difference.undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Generally, we shouldn't use undefinedundefinedfor..in for arrays.undefinedundefined

    undefinedundefined

    A word about "length"

    undefinedundefined

    The undefinedundefinedlength property automatically updates when we modify the array. To be precise, it is actually not the count of values in the array, but the greatest numeric index plus one.undefinedundefined

    undefinedundefined

    For instance, a single element with a large index gives a big length:

    undefinedundefined

    run let fruits = []; fruits[123] = "Apple";

    undefinedundefined

    alert( fruits.length ); // 124

    undefinedundefined

    Note that we usually don't use arrays like that.

    undefinedundefined

    Another interesting thing about the undefinedundefinedlength property is that it's writable.undefinedundefined

    undefinedundefined

    If we increase it manually, nothing interesting happens. But if we decrease it, the array is truncated. The process is irreversible, here's the example:

    undefinedundefined

    run let arr = [1, 2, 3, 4, 5];

    undefinedundefined

    arr.length = 2; // truncate to 2 elements alert( arr ); // [1, 2]

    undefinedundefined

    arr.length = 5; // return length back alert( arr[3] ); // undefined: the values do not return

    undefinedundefined

    So, the simplest way to clear the array is: undefinedundefinedarr.length = 0;.undefinedundefined

    undefinedundefined

    new Array() [#new-array]

    undefinedundefined

    There is one more syntax to create an array:

    undefinedundefinedundefinedundefined

    It's rarely used, because square brackets undefinedundefined[] are shorter. Also there's a tricky feature with it.undefinedundefined

    undefinedundefined

    If undefinedundefinednew Array is called with a single argument which is a number, then it creates an array undefinedundefinedwithout items, but with the given length.undefinedundefined

    undefinedundefined

    Let's see how one can shoot themself in the foot:

    undefinedundefined

    run let arr = new Array(2); // will it create an array of [2] ?

    undefinedundefined

    alert( arr[0] ); // undefined! no elements.

    undefinedundefined

    alert( arr.length ); // length 2

    undefinedundefined

    In the code above, undefinedundefinednew Array(number) has all elements undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    To evade such surprises, we usually use square brackets, unless we really know what we're doing.

    undefinedundefined

    Multidimensional arrays

    undefinedundefined

    Arrays can have items that are also arrays. We can use it for multidimensional arrays, for example to store matrices:

    undefinedundefined

    run let matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9]];

    undefinedundefined

    alert( matrix[1][1] ); // 5, the central element

    undefinedundefined

    toString

    undefinedundefined

    Arrays have their own implementation of undefinedundefinedtoString method that returns a comma-separated list of elements.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = [1, 2, 3];

    undefinedundefined

    alert( arr ); // 1,2,3 alert( String(arr) === ‘1,2,3' ); // true

    undefinedundefined

    Also, let's try this:

    undefinedundefined

    undefinedundefinedjs run alert( [] + 1 ); // "1" alert( [1] + 1 ); // "11" alert( [1,2] + 1 ); // "1,21"undefinedundefined

    undefinedundefined

    Arrays do not have undefinedundefinedSymbol.toPrimitive, neither a viable undefinedundefinedvalueOf, they implement only undefinedundefinedtoString conversion, so here undefinedundefined[] becomes an empty string, undefinedundefined[1] becomes undefinedundefined"1" and undefinedundefined[1,2] becomes undefinedundefined"1,2".undefinedundefined

    undefinedundefined

    When the binary plus undefinedundefined"+" operator adds something to a string, it converts it to a string as well, so the next step looks like this:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( "" + 1 ); // "1" alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21"undefinedundefined

    undefinedundefined

    Don't compare arrays with ==

    undefinedundefined

    Arrays in JavaScript, unlike some other programming languages, shouldn't be compared with operator undefinedundefined==.undefinedundefined

    undefinedundefined

    This operator has no special treatment for arrays, it works with them as with any objects.

    undefinedundefined

    Let's recall the rules:

    undefinedundefined
      undefinedundefined
    • Two objects are equal undefinedundefined== only if they're references to the same object.undefinedundefined
    • undefinedundefined
    • If one of the arguments of undefinedundefined== is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter undefinedundefinedinfo:object-toprimitive.undefinedundefined
    • undefinedundefined
    • …With an exception of undefinedundefinednull and undefinedundefinedundefined that equal undefinedundefined== each other and nothing else.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The strict comparison undefinedundefined=== is even simpler, as it doesn't convert types.undefinedundefined

    undefinedundefined

    So, if we compare arrays with undefinedundefined==, they are never the same, unless we compare two variables that reference exactly the same array.undefinedundefined

    undefinedundefined

    For example: undefinedundefinedjs run alert( [] == [] ); // false alert( [0] == [0] ); // falseundefinedundefined

    undefinedundefined

    These arrays are technically different objects. So they aren't equal. The undefinedundefined== operator doesn't do item-by-item comparison.undefinedundefined

    undefinedundefined

    Comparison with primitives may give seemingly strange results as well:

    undefinedundefined

    run alert( 0 == [] ); // true

    undefinedundefined

    alert(‘0' == [] ); // false

    undefinedundefined

    Here, in both cases, we compare a primitive with an array object. So the array undefinedundefined[] gets converted to primitive for the purpose of comparison and becomes an empty string undefinedundefined''.undefinedundefined

    undefinedundefined

    Then the comparison process goes on with the primitives, as described in the chapter undefinedundefinedinfo:type-conversions:undefinedundefined

    undefinedundefined

    run // after [] was converted to '' alert( 0 == '' ); // true, as '' becomes converted to number 0

    undefinedundefined

    alert(‘0' == '' ); // false, no type conversion, different strings

    undefinedundefined

    So, how to compare arrays?

    undefinedundefined

    That's simple: don't use the undefinedundefined== operator. Instead, compare them item-by-item in a loop or using iteration methods explained in the next chapter.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Array is a special kind of object, suited to storing and managing ordered data items.

    undefinedundefinedundefinedundefined

    We can use an array as a deque with the following operations:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedpush(...items) adds undefinedundefineditems to the end.undefinedundefined
    • undefinedundefined
    • undefinedundefinedpop() removes the element from the end and returns it.undefinedundefined
    • undefinedundefined
    • undefinedundefinedshift() removes the element from the beginning and returns it.undefinedundefined
    • undefinedundefined
    • undefinedundefinedunshift(...items) adds undefinedundefineditems to the beginning.undefinedundefined
    • undefinedundefined
    undefinedundefined

    To loop over the elements of the array: - undefinedundefinedfor (let i=0; i<arr.length; i++) - works fastest, old-browser-compatible. - undefinedundefinedfor (let item of arr) - the modern syntax for items only, - undefinedundefinedfor (let i in arr) - never use.undefinedundefined

    undefinedundefined

    To compare arrays, don't use the undefinedundefined== operator (as well as undefinedundefined>, undefinedundefined< and others), as they have no special treatment for arrays. They handle them as any objects, and it's not what we usually want.undefinedundefined

    undefinedundefined

    Instead you can use undefinedundefinedfor..of loop to compare arrays item-by-item.undefinedundefined

    undefinedundefined

    We will continue with arrays and study more methods to add, remove, extract elements and sort arrays in the next chapter undefinedundefinedinfo:array-methods.undefinedundefined

    undefinedundefined

    Array methods

    undefinedundefined

    Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups.

    undefinedundefined

    Add/remove items

    undefinedundefined

    We already know methods that add and remove items from the beginning or the end:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedarr.push(...items) - adds items to the end,undefinedundefined
    • undefinedundefined
    • undefinedundefinedarr.pop() - extracts an item from the end,undefinedundefined
    • undefinedundefined
    • undefinedundefinedarr.shift() - extracts an item from the beginning,undefinedundefined
    • undefinedundefined
    • undefinedundefinedarr.unshift(...items) - adds items to the beginning.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Here are a few others.

    undefinedundefined

    splice

    undefinedundefined

    How to delete an element from the array?

    undefinedundefined

    The arrays are objects, so we can try to use undefinedundefineddelete:undefinedundefined

    undefinedundefined

    run let arr = ["I", "go", "home"];

    undefinedundefined

    delete arr[1]; // remove "go"

    undefinedundefined

    alert( arr[1] ); // undefined

    undefinedundefined

    // now arr = ["I", , "home"]; alert( arr.length ); // 3

    undefinedundefined

    The element was removed, but the array still has 3 elements, we can see that undefinedundefinedarr.length == 3.undefinedundefined

    undefinedundefined

    That's natural, because undefinedundefineddelete obj.key removes a value by the undefinedundefinedkey. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.undefinedundefined

    undefinedundefined

    So, special methods should be used.

    undefinedundefined

    The undefinedundefinedarr.splice method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    It modifies undefinedundefinedarr starting from the index undefinedundefinedstart: removes undefinedundefineddeleteCount elements and then inserts undefinedundefinedelem1, ..., elemN at their place. Returns the array of removed elements.undefinedundefined

    undefinedundefined

    This method is easy to grasp by examples.

    undefinedundefined

    Let's start with the deletion:

    undefinedundefined

    run let arr = ["I", "study", "JavaScript"];

    undefinedundefined

    undefinedundefined! arr.splice(1, 1); // from index 1 remove 1 element undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( arr ); // ["I", "JavaScript"]

    undefinedundefined

    Easy, right? Starting from the index undefinedundefined1 it removed undefinedundefined1 element.undefinedundefined

    undefinedundefined

    In the next example we remove 3 elements and replace them with the other two:

    undefinedundefined

    run let arr = [undefinedundefined!"I", "study", "JavaScript",undefinedundefined/! "right", "now"];undefinedundefined

    undefinedundefined

    // remove 3 first elements and replace them with another arr.splice(0, 3, "Let's", "dance");

    undefinedundefined

    alert( arr ) // now [undefinedundefined!"Let's", "dance"undefinedundefined/!, "right", "now"] undefinedundefined

    undefinedundefined

    Here we can see that undefinedundefinedsplice returns the array of removed elements:undefinedundefined

    undefinedundefined

    run let arr = [undefinedundefined!"I", "study",undefinedundefined/! "JavaScript", "right", "now"];undefinedundefined

    undefinedundefined

    // remove 2 first elements let removed = arr.splice(0, 2);

    undefinedundefined

    alert( removed ); // "I", "study" <- array of removed elements

    undefinedundefined

    The undefinedundefinedsplice method is also able to insert the elements without any removals. For that we need to set undefinedundefineddeleteCount to undefinedundefined0:undefinedundefined

    undefinedundefined

    run let arr = ["I", "study", "JavaScript"];

    undefinedundefined

    // from index 2 // delete 0 // then insert "complex" and "language" arr.splice(2, 0, "complex", "language");

    undefinedundefined

    alert( arr ); // "I", "study", "complex", "language", "JavaScript"

    undefinedundefined

    smart header="Negative indexes allowed" Here and in other array methods, negative indexes are allowed. They specify the position from the end of the array, like here:

    undefinedundefined

    run let arr = [1, 2, 5];

    undefinedundefined

    // from index -1 (one step from the end) // delete 0 elements, // then insert 3 and 4 arr.splice(-1, 0, 3, 4);

    undefinedundefined

    alert( arr ); // 1,2,3,4,5

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    slice

    undefinedundefined

    The method undefinedundefinedarr.slice is much simpler than similar-looking undefinedundefinedarr.splice.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedarr.undefinedundefinedslice([start]undefinedundefined, [end])undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It returns a new array copying to it all items from index undefinedundefinedstart to undefinedundefinedend (not including undefinedundefinedend). Both undefinedundefinedstart and undefinedundefinedend can be negative, in that case position from array end is assumed.undefinedundefined

    undefinedundefined

    It's similar to a string method undefinedundefinedstr.slice, but instead of substrings it makes subarrays.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = ["t", "e", "s", "t"];

    undefinedundefined

    alert( arr.slice(1, 3) ); // e,s (copy from 1 to 3)

    undefinedundefined

    alert( arr.slice(-2) ); // s,t (copy from -2 till the end)

    undefinedundefined

    We can also call it without arguments: undefinedundefinedarr.slice() creates a copy of undefinedundefinedarr. That's often used to obtain a copy for further transformations that should not affect the original array.undefinedundefined

    undefinedundefined

    concat

    undefinedundefined

    The method undefinedundefinedarr.concat creates a new array that includes values from other arrays and additional items.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedarr.undefinedundefinedconcat(arg1undefinedundefined,undefinedundefinedarg2...)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It accepts any number of arguments - either arrays or values.

    undefinedundefined

    The result is a new array containing items from undefinedundefinedarr, then undefinedundefinedarg1, undefinedundefinedarg2 etc.undefinedundefined

    undefinedundefined

    If an argument undefinedundefinedargN is an array, then all its elements are copied. Otherwise, the argument itself is copied.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = [1, 2];

    undefinedundefined

    // create an array from: arr and [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4

    undefinedundefined

    // create an array from: arr and [3,4] and [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6

    undefinedundefined

    // create an array from: arr and [3,4], then add values 5 and 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6

    undefinedundefined

    Normally, it only copies elements from arrays. Other objects, even if they look like arrays, are added as a whole:

    undefinedundefined

    run let arr = [1, 2];

    undefinedundefined

    let arrayLike = { 0: "something", length: 1 };

    undefinedundefined

    alert( arr.concat(arrayLike) ); // 1,2,[object Object]

    undefinedundefined

    …But if an array-like object has a special undefinedundefinedSymbol.isConcatSpreadable property, then it's treated as an array by undefinedundefinedconcat: its elements are added instead:undefinedundefined

    undefinedundefined

    run let arr = [1, 2];

    undefinedundefined

    let arrayLike = { 0: "something", 1: "else", undefinedundefined! [Symbol.isConcatSpreadable]: true, undefinedundefined/! length: 2 };undefinedundefined

    undefinedundefined

    alert( arr.concat(arrayLike) ); // 1,2,something,else

    undefinedundefined

    Iterate: forEach

    undefinedundefined

    The undefinedundefinedarr.forEach method allows to run a function for every element of the array.undefinedundefined

    undefinedundefined

    The syntax:

    undefinedundefinedundefinedundefined

    For instance, this shows each element of the array:

    undefinedundefined

    undefinedundefinedjs run // for each element call alert ["Bilbo", "Gandalf", "Nazgul"].forEach(alert);undefinedundefined

    undefinedundefined

    And this code is more elaborate about their positions in the target array:

    undefinedundefined

    undefinedundefinedjs run ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { alert(`${item} is at index ${index} in ${array}`); });undefinedundefined

    undefinedundefined

    The result of the function (if it returns any) is thrown away and ignored.

    undefinedundefined

    Searching in array

    undefinedundefined

    Now let's cover methods that search in an array.

    undefinedundefined

    indexOf/lastIndexOf and includes

    undefinedundefined

    The methods undefinedundefinedarr.indexOf, undefinedundefinedarr.lastIndexOf and undefinedundefinedarr.includes have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedarr.indexOf(item, from) - looks for undefinedundefineditem starting from index undefinedundefinedfrom, and returns the index where it was found, otherwise undefinedundefined-1.undefinedundefined
    • undefinedundefined
    • undefinedundefinedarr.lastIndexOf(item, from) - same, but looks for from right to left.undefinedundefined
    • undefinedundefined
    • undefinedundefinedarr.includes(item, from) - looks for undefinedundefineditem starting from index undefinedundefinedfrom, returns undefinedundefinedtrue if found.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = [1, 0, false];

    undefinedundefined

    alert( arr.indexOf(0) ); // 1 alert( arr.indexOf(false) ); // 2 alert( arr.indexOf(null) ); // -1

    undefinedundefined

    alert( arr.includes(1) ); // true

    undefinedundefined

    Note that the methods use undefinedundefined=== comparison. So, if we look for undefinedundefinedfalse, it finds exactly undefinedundefinedfalse and not the zero.undefinedundefined

    undefinedundefined

    If we want to check for inclusion, and don't want to know the exact index, then undefinedundefinedarr.includes is preferred.undefinedundefined

    undefinedundefined

    Also, a very minor difference of undefinedundefinedincludes is that it correctly handles undefinedundefinedNaN, unlike undefinedundefinedindexOf/lastIndexOf:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN) alert( arr.includes(NaN) );// true (correct)undefinedundefined

    undefinedundefined

    find and findIndex

    undefinedundefined

    Imagine we have an array of objects. How do we find an object with the specific condition?

    undefinedundefined

    Here the undefinedundefinedarr.find(fn) method comes in handy.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    The function is called for elements of the array, one after another:

    undefinedundefined
      undefinedundefined
    • undefinedundefineditem is the element.undefinedundefined
    • undefinedundefined
    • undefinedundefinedindex is its index.undefinedundefined
    • undefinedundefined
    • undefinedundefinedarray is the array itself.undefinedundefined
    • undefinedundefined
    undefinedundefined

    If it returns undefinedundefinedtrue, the search is stopped, the undefinedundefineditem is returned. If nothing found, undefinedundefinedundefined is returned.undefinedundefined

    undefinedundefined

    For example, we have an array of users, each with the fields undefinedundefinedid and undefinedundefinedname. Let's find the one with undefinedundefinedid == 1:undefinedundefined

    undefinedundefined

    run let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"}];

    undefinedundefined

    let user = users.find(item => item.id == 1);

    undefinedundefined

    alert(user.name); // John

    undefinedundefined

    In real life arrays of objects is a common thing, so the undefinedundefinedfind method is very useful.undefinedundefined

    undefinedundefined

    Note that in the example we provide to undefinedundefinedfind the function undefinedundefineditem => item.id == 1 with one argument. That's typical, other arguments of this function are rarely used.undefinedundefined

    undefinedundefined

    The undefinedundefinedarr.findIndex method is essentially the same, but it returns the index where the element was found instead of the element itself and undefinedundefined-1 is returned when nothing is found.undefinedundefined

    undefinedundefined

    filter

    undefinedundefined

    The undefinedundefinedfind method looks for a single (first) element that makes the function return undefinedundefinedtrue.undefinedundefined

    undefinedundefined

    If there may be many, we can use undefinedundefinedarr.filter(fn).undefinedundefined

    undefinedundefined

    The syntax is similar to undefinedundefinedfind, but undefinedundefinedfilter returns an array of all matching elements:undefinedundefined

    undefinedundefinedundefinedundefined

    For instance:

    undefinedundefined

    run let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"}];

    undefinedundefined

    // returns array of the first two users let someUsers = users.filter(item => item.id < 3);

    undefinedundefined

    alert(someUsers.length); // 2

    undefinedundefined

    Transform an array

    undefinedundefined

    Let's move on to methods that transform and reorder an array.

    undefinedundefined

    map

    undefinedundefined

    The undefinedundefinedarr.map method is one of the most useful and often used.undefinedundefined

    undefinedundefined

    It calls the function for each element of the array and returns the array of results.

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    For instance, here we transform each element into its length:

    undefinedundefined

    undefinedundefinedjs run let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6undefinedundefined

    undefinedundefined

    sort(fn)

    undefinedundefined

    The call to undefinedundefinedarr.sort() sorts the array undefinedundefinedin place, changing its element order.undefinedundefined

    undefinedundefined

    It also returns the sorted array, but the returned value is usually ignored, as undefinedundefinedarr itself is modified.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = [ 1, 2, 15 ];

    undefinedundefined

    // the method reorders the content of arr arr.sort();

    undefinedundefined

    alert( arr ); // undefinedundefined!1, 15, 2undefinedundefined/! undefinedundefined

    undefinedundefined

    Did you notice anything strange in the outcome?

    undefinedundefined

    The order became undefinedundefined1, 15, 2. Incorrect. But why?undefinedundefined

    undefinedundefined

    undefinedundefinedThe items are sorted as strings by default.undefinedundefined

    undefinedundefined

    Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed undefinedundefined"2" > "15".undefinedundefined

    undefinedundefined

    To use our own sorting order, we need to supply a function as the argument of undefinedundefinedarr.sort().undefinedundefined

    undefinedundefined

    The function should compare two arbitrary values and return:

    undefinedundefinedundefinedundefined

    For instance, to sort as numbers:

    undefinedundefined

    run function compareNumeric(a, b) { if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; }

    undefinedundefined

    let arr = [ 1, 2, 15 ];

    undefinedundefined

    undefinedundefined! arr.sort(compareNumeric); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(arr); // undefinedundefined!1, 2, 15undefinedundefined/! undefinedundefined

    undefinedundefined

    Now it works as intended.

    undefinedundefined

    Let's step aside and think what's happening. The undefinedundefinedarr can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of undefinedundefinedsome items. To sort it, we need an undefinedundefinedordering function that knows how to compare its elements. The default is a string order.undefinedundefined

    undefinedundefined

    The undefinedundefinedarr.sort(fn) method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized undefinedundefinedquicksort or undefinedundefinedTimsort most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the undefinedundefinedfn which does the comparison.undefinedundefined

    undefinedundefined

    By the way, if we ever want to know which elements are compared - nothing prevents from alerting them:

    undefinedundefined

    undefinedundefinedjs run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); return a - b; });undefinedundefined

    undefinedundefined

    The algorithm may compare an element with multiple others in the process, but it tries to make as few comparisons as possible.

    undefinedundefined

    smart header="A comparison function may return any number" Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less".

    undefinedundefined

    That allows to write shorter functions:

    undefinedundefined

    run let arr = [ 1, 2, 15 ];

    undefinedundefined

    arr.sort(function(a, b) { return a - b; });

    undefinedundefined

    alert(arr); // undefinedundefined!1, 2, 15undefinedundefined/!undefinedundefined

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    smart header="Arrow functions for the best" Remember undefinedundefinedarrow functions? We can use them here for neater sorting:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedarr.undefinedundefinedsort( (aundefinedundefined, b) undefinedundefined=> a undefinedundefined- b )undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    This works exactly the same as the longer version above.

    undefinedundefined

    undefinedundefinedsmart header="UselocaleCompare` for strings" Remember undefinedundefinedstrings comparison algorithm? It compares letters by their codes by default.undefinedundefined

    undefinedundefined

    For many alphabets, it's better to use undefinedundefinedstr.localeCompare method to correctly sort letters, such as undefinedundefinedÖ.undefinedundefined

    undefinedundefined

    For example, let's sort a few countries in German:

    undefinedundefined

    run let countries = [‘Österreich', ‘Andorra', ‘Vietnam'];

    undefinedundefined

    alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong)

    undefinedundefined

    alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!)

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    reverse

    undefinedundefined

    The method undefinedundefinedarr.reverse reverses the order of elements in undefinedundefinedarr.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = [1, 2, 3, 4, 5]; arr.reverse();

    undefinedundefined

    alert( arr ); // 5,4,3,2,1

    undefinedundefined

    It also returns the array undefinedundefinedarr after the reversal.undefinedundefined

    undefinedundefined

    split and join

    undefinedundefined

    Here's the situation from real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: undefinedundefinedJohn, Pete, Mary. But for us an array of names would be much more comfortable than a single string. How to get it?undefinedundefined

    undefinedundefined

    The undefinedundefinedstr.split(delim) method does exactly that. It splits the string into an array by the given delimiter undefinedundefineddelim.undefinedundefined

    undefinedundefined

    In the example below, we split by a comma followed by space:

    undefinedundefined

    run let names = ‘Bilbo, Gandalf, Nazgul';

    undefinedundefined

    let arr = names.split(‘,''');

    undefinedundefined

    for (let name of arr) { alert( undefinedundefinedA message to ${name}. ); // A message to Bilbo (and other names) } undefinedundefined

    undefinedundefined

    The undefinedundefinedsplit method has an optional second numeric argument - a limit on the array length. If it is provided, then the extra elements are ignored. In practice it is rarely used though:undefinedundefined

    undefinedundefined

    run let arr = ‘Bilbo, Gandalf, Nazgul, Saruman'.split(‘,''', 2);

    undefinedundefined

    alert(arr); // Bilbo, Gandalf

    undefinedundefined

    undefinedundefinedsmart header="Split into letters" The call tosplit(s)undefinedundefinedwith an emptys` would split the string into an array of letters:undefinedundefined

    undefinedundefined

    run let str = "test";

    undefinedundefined

    alert( str.split('') ); // t,e,s,t

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    The call undefinedundefinedarr.join(glue) does the reverse to undefinedundefinedsplit. It creates a string of undefinedundefinedarr items joined by undefinedundefinedglue between them.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arr = [‘Bilbo', ‘Gandalf', ‘Nazgul'];

    undefinedundefined

    let str = arr.join(‘;'''); // glue the array into a string using ;

    undefinedundefined

    alert( str ); // Bilbo;Gandalf;Nazgul

    undefinedundefined

    reduce/reduceRight

    undefinedundefined

    When we need to iterate over an array - we can use undefinedundefinedforEach, undefinedundefinedfor or undefinedundefinedfor..of.undefinedundefined

    undefinedundefined

    When we need to iterate and return the data for each element - we can use undefinedundefinedmap.undefinedundefined

    undefinedundefined

    The methods undefinedundefinedarr.reduce and undefinedundefinedarr.reduceRight also belong to that breed, but are a little bit more intricate. They are used to calculate a single value based on the array.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    The function is applied to all array elements one after another and "carries on" its result to the next call.

    undefinedundefined

    Arguments:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedaccumulator - is the result of the previous function call, equals undefinedundefinedinitial the first time (if undefinedundefinedinitial is provided).undefinedundefined
    • undefinedundefined
    • undefinedundefineditem - is the current array item.undefinedundefined
    • undefinedundefined
    • undefinedundefinedindex - is its position.undefinedundefined
    • undefinedundefined
    • undefinedundefinedarray - is the array.undefinedundefined
    • undefinedundefined
    undefinedundefined

    As function is applied, the result of the previous function call is passed to the next one as the first argument.

    undefinedundefined

    So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of undefinedundefinedreduce.undefinedundefined

    undefinedundefined

    Sounds complicated?

    undefinedundefined

    The easiest way to grasp that is by example.

    undefinedundefined

    Here we get a sum of an array in one line:

    undefinedundefined

    run let arr = [1, 2, 3, 4, 5];

    undefinedundefined

    let result = arr.reduce((sum, current) => sum + current, 0);

    undefinedundefined

    alert(result); // 15

    undefinedundefined

    The function passed to undefinedundefinedreduce uses only 2 arguments, that's typically enough.undefinedundefined

    undefinedundefined

    Let's see the details of what's going on.

    undefinedundefined
      undefinedundefined
    1. On the first run, undefinedundefinedsum is the undefinedundefinedinitial value (the last argument of undefinedundefinedreduce), equals undefinedundefined0, and undefinedundefinedcurrent is the first array element, equals undefinedundefined1. So the function result is undefinedundefined1.undefinedundefined
    2. undefinedundefined
    3. On the second run, undefinedundefinedsum = 1, we add the second array element (undefinedundefined2) to it and return.undefinedundefined
    4. undefinedundefined
    5. On the 3rd run, undefinedundefinedsum = 3 and we add one more element to it, and so on…undefinedundefined
    6. undefinedundefined
    undefinedundefined

    The calculation flow:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Or in the form of a table, where each row represents a function call on the next array element:

    undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined
    undefinedundefinedsumundefinedundefined undefinedundefinedcurrentundefinedundefined result
    the first call undefinedundefined0undefinedundefined undefinedundefined1undefinedundefined undefinedundefined1undefinedundefined
    the second callundefinedundefined1undefinedundefined undefinedundefined2undefinedundefined undefinedundefined3undefinedundefined
    the third callundefinedundefined3undefinedundefined undefinedundefined3undefinedundefined undefinedundefined6undefinedundefined
    the fourth callundefinedundefined6undefinedundefined undefinedundefined4undefinedundefined undefinedundefined10undefinedundefined
    the fifth callundefinedundefined10undefinedundefined undefinedundefined5undefinedundefined undefinedundefined15undefinedundefined
    undefinedundefined

    Here we can clearly see how the result of the previous call becomes the first argument of the next one.

    undefinedundefined

    We also can omit the initial value:

    undefinedundefined

    run let arr = [1, 2, 3, 4, 5];

    undefinedundefined

    // removed initial value from reduce (no 0) let result = arr.reduce((sum, current) => sum + current);

    undefinedundefined

    alert( result ); // 15

    undefinedundefined

    The result is the same. That's because if there's no initial, then undefinedundefinedreduce takes the first element of the array as the initial value and starts the iteration from the 2nd element.undefinedundefined

    undefinedundefined

    The calculation table is the same as above, minus the first row.

    undefinedundefined

    But such use requires an extreme care. If the array is empty, then undefinedundefinedreduce call without initial value gives an error.undefinedundefined

    undefinedundefined

    Here's an example:

    undefinedundefined

    run let arr = [];

    undefinedundefined

    // Error: Reduce of empty array with no initial value // if the initial value existed, reduce would return it for the empty arr. arr.reduce((sum, current) => sum + current);

    undefinedundefined

    So it's advised to always specify the initial value.

    undefinedundefined

    The method undefinedundefinedarr.reduceRight does the same, but goes from right to left.undefinedundefined

    undefinedundefined

    Array.isArray

    undefinedundefined

    Arrays do not form a separate language type. They are based on objects.

    undefinedundefined

    So undefinedundefinedtypeof does not help to distinguish a plain object from an array:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert(typeof {}); // object alert(typeof []); // sameundefinedundefined

    undefinedundefined

    …But arrays are used so often that there's a special method for that: undefinedundefinedArray.isArray(value). It returns undefinedundefinedtrue if the undefinedundefinedvalue is an array, and undefinedundefinedfalse otherwise.undefinedundefined

    undefinedundefined

    run alert(Array.isArray({})); // false

    undefinedundefined

    alert(Array.isArray([])); // true

    undefinedundefined

    Most methods support "thisArg"

    undefinedundefined

    Almost all array methods that call functions - like undefinedundefinedfind, undefinedundefinedfilter, undefinedundefinedmap, with a notable exception of undefinedundefinedsort, accept an optional additional parameter undefinedundefinedthisArg.undefinedundefined

    undefinedundefined

    That parameter is not explained in the sections above, because it's rarely used. But for completeness we have to cover it.

    undefinedundefined

    Here's the full syntax of these methods:

    undefinedundefinedundefinedundefined

    The value of undefinedundefinedthisArg parameter becomes undefinedundefinedthis for undefinedundefinedfunc.undefinedundefined

    undefinedundefined

    For example, here we use a method of undefinedundefinedarmy object as a filter, and undefinedundefinedthisArg passes the context:undefinedundefined

    undefinedundefined

    run let army = { minAge: 18, maxAge: 27, canJoin(user) { return user.age >= this.minAge && user.age < this.maxAge; } };

    undefinedundefined

    let users = [ {age: 16}, {age: 20}, {age: 23}, {age: 30}];

    undefinedundefined

    undefinedundefined! // find users, for who army.canJoin returns true let soldiers = users.filter(army.canJoin, army); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(soldiers.length); // 2 alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23

    undefinedundefined

    If in the example above we used undefinedundefinedusers.filter(army.canJoin), then undefinedundefinedarmy.canJoin would be called as a standalone function, with undefinedundefinedthis=undefined, thus leading to an instant error.undefinedundefined

    undefinedundefined

    A call to undefinedundefinedusers.filter(army.canJoin, army) can be replaced with undefinedundefinedusers.filter(user => army.canJoin(user)), that does the same. The latter is used more often, as it's a bit easier to understand for most people.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    A cheat sheet of array methods:

    undefinedundefined
      undefinedundefined
    • To add/remove elements: undefinedundefined
        undefinedundefined
      • undefinedundefinedpush(...items) - adds items to the end,undefinedundefined
      • undefinedundefined
      • undefinedundefinedpop() - extracts an item from the end,undefinedundefined
      • undefinedundefined
      • undefinedundefinedshift() - extracts an item from the beginning,undefinedundefined
      • undefinedundefined
      • undefinedundefinedunshift(...items) - adds items to the beginning.undefinedundefined
      • undefinedundefined
      • undefinedundefinedsplice(pos, deleteCount, ...items) - at index undefinedundefinedpos deletes undefinedundefineddeleteCount elements and inserts undefinedundefineditems.undefinedundefined
      • undefinedundefined
      • undefinedundefinedslice(start, end) - creates a new array, copies elements from index undefinedundefinedstart till undefinedundefinedend (not inclusive) into it.undefinedundefined
      • undefinedundefined
      • undefinedundefinedconcat(...items) - returns a new array: copies all members of the current one and adds undefinedundefineditems to it. If any of undefinedundefineditems is an array, then its elements are taken.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • To search among elements: undefinedundefined
        undefinedundefined
      • undefinedundefinedindexOf/lastIndexOf(item, pos) - look for undefinedundefineditem starting from position undefinedundefinedpos, return the index or undefinedundefined-1 if not found.undefinedundefined
      • undefinedundefined
      • undefinedundefinedincludes(value) - returns undefinedundefinedtrue if the array has undefinedundefinedvalue, otherwise undefinedundefinedfalse.undefinedundefined
      • undefinedundefined
      • undefinedundefinedfind/filter(func) - filter elements through the function, return first/all values that make it return undefinedundefinedtrue.undefinedundefined
      • undefinedundefined
      • undefinedundefinedfindIndex is like undefinedundefinedfind, but returns the index instead of a value.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • To iterate over elements: undefinedundefined
        undefinedundefined
      • undefinedundefinedforEach(func) - calls undefinedundefinedfunc for every element, does not return anything.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • To transform the array: undefinedundefined
        undefinedundefined
      • undefinedundefinedmap(func) - creates a new array from results of calling undefinedundefinedfunc for every element.undefinedundefined
      • undefinedundefined
      • undefinedundefinedsort(func) - sorts the array in-place, then returns it.undefinedundefined
      • undefinedundefined
      • undefinedundefinedreverse() - reverses the array in-place, then returns it.undefinedundefined
      • undefinedundefined
      • undefinedundefinedsplit/join - convert a string to array and back.undefinedundefined
      • undefinedundefined
      • undefinedundefinedreduce/reduceRight(func, initial) - calculate a single value over the array by calling undefinedundefinedfunc for each element and passing an intermediate result between the calls.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Additionally: undefinedundefined
        undefinedundefined
      • undefinedundefinedArray.isArray(arr) checks undefinedundefinedarr for being an array.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Please note that methods undefinedundefinedsort, undefinedundefinedreverse and undefinedundefinedsplice modify the array itself.undefinedundefined

    undefinedundefined

    These methods are the most used ones, they cover 99% of use cases. But there are few others:

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      undefinedundefinedarr.some(fn)/undefinedundefinedarr.every(fn) check the array.undefinedundefined

      undefinedundefined

      The function undefinedundefinedfn is called on each element of the array similar to undefinedundefinedmap. If any/all results are undefinedundefinedtrue, returns undefinedundefinedtrue, otherwise undefinedundefinedfalse.undefinedundefined

      undefinedundefined

      These methods behave sort of like undefinedundefined|| and undefinedundefined&& operators: if undefinedundefinedfn returns a truthy value, undefinedundefinedarr.some() immediately returns undefinedundefinedtrue and stops iterating over the rest of items; if undefinedundefinedfn returns a falsy value, undefinedundefinedarr.every() immediately returns undefinedundefinedfalse and stops iterating over the rest of items as well.undefinedundefined

      undefinedundefined

      We can use undefinedundefinedevery to compare arrays: run function arraysEqual(arr1, arr2) { return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); }undefinedundefined

      undefinedundefined

      alert( arraysEqual([1, 2], [1, 2])); // true

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedarr.fill(value, start, end) - fills the array with repeating undefinedundefinedvalue from index undefinedundefinedstart to undefinedundefinedend.undefinedundefined

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedarr.copyWithin(target, start, end) - copies its elements from position undefinedundefinedstart till position undefinedundefinedend into undefinedundefineditself, at position undefinedundefinedtarget (overwrites existing).undefinedundefined

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedarr.flat(depth)/undefinedundefinedarr.flatMap(fn) create a new flat array from a multidimensional array.undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    For the full list, see the undefinedundefinedmanual.undefinedundefined

    undefinedundefined

    From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier.

    undefinedundefined

    Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods.

    undefinedundefined

    Afterwards whenever you need to do something with an array, and you don't know how - come here, look at the cheat sheet and find the right method. Examples will help you to write it correctly. Soon you'll automatically remember the methods, without specific efforts from your side.

    undefinedundefined

    Iterables

    undefinedundefined

    undefinedundefinedIterable objects are a generalization of arrays. That's a concept that allows us to make any object useable in a undefinedundefinedfor..of loop.undefinedundefined

    undefinedundefined

    Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable.

    undefinedundefined

    If an object isn't technically an array, but represents a collection (list, set) of something, then undefinedundefinedfor..of is a great syntax to loop over it, so let's see how to make it work.undefinedundefined

    undefinedundefined

    Symbol.iterator

    undefinedundefined

    We can easily grasp the concept of iterables by making one of our own.

    undefinedundefined

    For instance, we have an object that is not an array, but looks suitable for undefinedundefinedfor..of.undefinedundefined

    undefinedundefined

    Like a undefinedundefinedrange object that represents an interval of numbers:undefinedundefined

    undefinedundefinedundefinedundefined

    To make the undefinedundefinedrange object iterable (and thus let undefinedundefinedfor..of work) we need to add a method to the object named undefinedundefinedSymbol.iterator (a special built-in symbol just for that).undefinedundefined

    undefinedundefined
      undefinedundefined
    1. When undefinedundefinedfor..of starts, it calls that method once (or errors if not found). The method must return an undefinedundefinediterator - an object with the method undefinedundefinednext.undefinedundefined
    2. undefinedundefined
    3. Onward, undefinedundefinedfor..of works undefinedundefinedonly with that returned object.undefinedundefined
    4. undefinedundefined
    5. When undefinedundefinedfor..of wants the next value, it calls undefinedundefinednext() on that object.undefinedundefined
    6. undefinedundefined
    7. The result of undefinedundefinednext() must have the form undefinedundefined{done: Boolean, value: any}, where undefinedundefineddone=true means that the iteration is finished, otherwise undefinedundefinedvalue is the next value.undefinedundefined
    8. undefinedundefined
    undefinedundefined

    Here's the full implementation for undefinedundefinedrange with remarks:undefinedundefined

    undefinedundefined

    run let range = { from: 1, to: 5 };

    undefinedundefined

    // 1. call to for..of initially calls this rangeundefinedundefinedSymbol.iterator = function() {undefinedundefined

    undefinedundefined

    // …it returns the iterator object: // 2. Onward, for..of works only with this iterator, asking it for next values return { current: this.from, last: this.to,

    undefinedundefined
    undefinedundefined// 3. next() is called on each iteration by the for..of loop
    next() {
    // 4. it should return the value as an object {done:.., value :...}
    if (this.current <= this.last) {
    return { done: false, value: this.current++ };
    } else {
    return { done: true };
    }
    }undefinedundefined
    undefinedundefined

    }; };

    undefinedundefined

    // now it works! for (let num of range) { alert(num); // 1, then 2, 3, 4, 5 }

    undefinedundefined

    Please note the core feature of iterables: separation of concerns.

    undefinedundefined
      undefinedundefined
    • The undefinedundefinedrange itself does not have the undefinedundefinednext() method.undefinedundefined
    • undefinedundefined
    • Instead, another object, a so-called "iterator" is created by the call to undefinedundefinedrange[Symbol.iterator](), and its undefinedundefinednext() generates values for the iteration.undefinedundefined
    • undefinedundefined
    undefinedundefined

    So, the iterator object is separate from the object it iterates over.

    undefinedundefined

    Technically, we may merge them and use undefinedundefinedrange itself as the iterator to make the code simpler.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    undefinedundefinedSymbol.iterator { this.current = this.from; return this; },undefinedundefined

    undefinedundefined

    next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } };

    undefinedundefined

    for (let num of range) { alert(num); // 1, then 2, 3, 4, 5 }

    undefinedundefined

    Now undefinedundefinedrange[Symbol.iterator]() returns the undefinedundefinedrange object itself: it has the necessary undefinedundefinednext() method and remembers the current iteration progress in undefinedundefinedthis.current. Shorter? Yes. And sometimes that's fine too.undefinedundefined

    undefinedundefined

    The downside is that now it's impossible to have two undefinedundefinedfor..of loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator - the object itself. But two parallel for-ofs is a rare thing, even in async scenarios.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Infinite iterators" Infinite iterators are also possible. For instance, therangeundefinedundefinedbecomes infinite forrange.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful.undefinedundefined

    undefinedundefined

    There are no limitations on undefinedundefinednext, it can return more and more values, that's normal.undefinedundefined

    undefinedundefined

    Of course, the undefinedundefinedfor..of loop over such an iterable would be endless. But we can always stop it using undefinedundefinedbreak. undefinedundefined

    undefinedundefined

    String is iterable

    undefinedundefined

    Arrays and strings are most widely used built-in iterables.

    undefinedundefined

    For a string, undefinedundefinedfor..of loops over its characters:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run for (let char of "test") { // triggers 4 times: once for each character alert( char ); // t, then e, then s, then t }undefinedundefined

    undefinedundefined

    And it works correctly with surrogate pairs!

    undefinedundefined

    undefinedundefinedjs run let str = '𝒳😂'; for (let char of str) { alert( char ); // 𝒳, and then 😂 }undefinedundefined

    undefinedundefined

    Calling an iterator explicitly

    undefinedundefined

    For deeper understanding, let's see how to use an iterator explicitly.

    undefinedundefined

    We'll iterate over a string in exactly the same way as undefinedundefinedfor..of, but with direct calls. This code creates a string iterator and gets values from it "manually":undefinedundefined

    undefinedundefined

    run let str = "Hello";

    undefinedundefined

    // does the same as // for (let char of str) alert(char);

    undefinedundefined

    undefinedundefined! let iterator = strundefinedundefinedSymbol.iterator; undefinedundefined/!undefinedundefined

    undefinedundefined

    while (true) { let result = iterator.next(); if (result.done) break; alert(result.value); // outputs characters one by one }

    undefinedundefined

    That is rarely needed, but gives us more control over the process than undefinedundefinedfor..of. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later.undefinedundefined

    undefinedundefined

    Iterables and array-likes [#array-like]

    undefinedundefined

    Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion.

    undefinedundefined
      undefinedundefined
    • undefinedundefinedIterables are objects that implement the undefinedundefinedSymbol.iterator method, as described above.undefinedundefined
    • undefinedundefined
    • undefinedundefinedArray-likes are objects that have indexes and undefinedundefinedlength, so they look like arrays.undefinedundefined
    • undefinedundefined
    undefinedundefined

    When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both.

    undefinedundefined

    For instance, strings are both iterable (undefinedundefinedfor..of works on them) and array-like (they have numeric indexes and undefinedundefinedlength).undefinedundefined

    undefinedundefined

    But an iterable may be not array-like. And vice versa an array-like may be not iterable.

    undefinedundefined

    For example, the undefinedundefinedrange in the example above is iterable, but not array-like, because it does not have indexed properties and undefinedundefinedlength.undefinedundefined

    undefinedundefined

    And here's the object that is array-like, but not iterable:

    undefinedundefined

    run let arrayLike = { // has indexes and length => array-like 0: "Hello", 1: "World", length: 2 };

    undefinedundefined

    undefinedundefined! // Error (no Symbol.iterator) for (let item of arrayLike) {} undefinedundefined/! undefinedundefined

    undefinedundefined

    Both iterables and array-likes are usually undefinedundefinednot arrays, they don't have undefinedundefinedpush, undefinedundefinedpop etc. That's rather inconvenient if we have such an object and want to work with it as with an array. E.g. we would like to work with undefinedundefinedrange using array methods. How to achieve that?undefinedundefined

    undefinedundefined

    Array.from

    undefinedundefined

    There's a universal method undefinedundefinedArray.from that takes an iterable or array-like value and makes a "real" undefinedundefinedArray from it. Then we can call array methods on it.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let arrayLike = { 0: "Hello", 1: "World", length: 2 };

    undefinedundefined

    undefinedundefined! let arr = Array.from(arrayLike); // (undefinedundefined) /!* alert(arr.pop()); // World (method works) undefinedundefined

    undefinedundefined

    undefinedundefinedArray.from at the line undefinedundefined(*) takes the object, examines it for being an iterable or array-like, then makes a new array and copies all items to it.undefinedundefined

    undefinedundefined

    The same happens for an iterable:

    undefinedundefinedundefinedundefined

    The full syntax for undefinedundefinedArray.from also allows us to provide an optional "mapping" function:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedArray.undefinedundefinedfrom(obj[undefinedundefined, mapFnundefinedundefined, thisArg])undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The optional second argument undefinedundefinedmapFn can be a function that will be applied to each element before adding it to the array, and undefinedundefinedthisArg allows us to set undefinedundefinedthis for it.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    Here we use undefinedundefinedArray.from to turn a string into an array of characters:undefinedundefined

    undefinedundefined

    run let str = ‘𝒳😂';

    undefinedundefined

    // splits str into array of characters let chars = Array.from(str);

    undefinedundefined

    alert(chars[0]); // 𝒳 alert(chars[1]); // 😂 alert(chars.length); // 2

    undefinedundefined

    Unlike undefinedundefinedstr.split, it relies on the iterable nature of the string and so, just like undefinedundefinedfor..of, correctly works with surrogate pairs.undefinedundefined

    undefinedundefined

    Technically here it does the same as:

    undefinedundefined

    run let str = ‘𝒳😂';

    undefinedundefined

    let chars = []; // Array.from internally does the same loop for (let char of str) { chars.push(char); }

    undefinedundefined

    alert(chars);

    undefinedundefined

    …But it is shorter.

    undefinedundefined

    We can even build surrogate-aware undefinedundefinedslice on it:undefinedundefined

    undefinedundefined

    run function slice(str, start, end) { return Array.from(str).slice(start, end).join(''); }

    undefinedundefined

    let str = ‘𝒳😂𩷶';

    undefinedundefined

    alert( slice(str, 1, 3) ); // 😂𩷶

    undefinedundefined

    // the native method does not support surrogate pairs alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs)

    undefinedundefined

    Summary

    undefinedundefined

    Objects that can be used in undefinedundefinedfor..of are called undefinedundefinediterable.undefinedundefined

    undefinedundefined
      undefinedundefined
    • Technically, iterables must implement the method named undefinedundefinedSymbol.iterator. undefinedundefined
        undefinedundefined
      • The result of undefinedundefinedobj[Symbol.iterator]() is called an undefinedundefinediterator. It handles further iteration process.undefinedundefined
      • undefinedundefined
      • An iterator must have the method named undefinedundefinednext() that returns an object undefinedundefined{done: Boolean, value: any}, here undefinedundefineddone:true denotes the end of the iteration process, otherwise the undefinedundefinedvalue is the next value.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • The undefinedundefinedSymbol.iterator method is called automatically by undefinedundefinedfor..of, but we also can do it directly.undefinedundefined
    • undefinedundefined
    • Built-in iterables like strings or arrays, also implement undefinedundefinedSymbol.iterator.undefinedundefined
    • undefinedundefined
    • String iterator knows about surrogate pairs.
    • undefinedundefined
    undefinedundefined

    Objects that have indexed properties and undefinedundefinedlength are called undefinedundefinedarray-like. Such objects may also have other properties and methods, but lack the built-in methods of arrays.undefinedundefined

    undefinedundefined

    If we look inside the specification - we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract.

    undefinedundefined

    undefinedundefinedArray.from(obj[, mapFn, thisArg]) makes a real undefinedundefinedArray from an iterable or array-like undefinedundefinedobj, and we can then use array methods on it. The optional arguments undefinedundefinedmapFn and undefinedundefinedthisArg allow us to apply a function to each item.undefinedundefined

    undefinedundefined

    Map and Set

    undefinedundefined

    Till now, we've learned about the following complex data structures:

    undefinedundefined
      undefinedundefined
    • Objects are used for storing keyed collections.
    • undefinedundefined
    • Arrays are used for storing ordered collections.
    • undefinedundefined
    undefinedundefined

    But that's not enough for real life. That's why undefinedundefinedMap and undefinedundefinedSet also exist.undefinedundefined

    undefinedundefined

    Map

    undefinedundefined

    undefinedundefinedMap is a collection of keyed data items, just like an undefinedundefinedObject. But the main difference is that undefinedundefinedMap allows keys of any type.undefinedundefined

    undefinedundefined

    Methods and properties are:

    undefinedundefined
      undefinedundefined
    • undefinedundefinednew Map() - creates the map.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.set(key, value) - stores the value by the key.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.get(key) - returns the value by the key, undefinedundefinedundefined if undefinedundefinedkey doesn't exist in map.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.has(key) - returns undefinedundefinedtrue if the undefinedundefinedkey exists, undefinedundefinedfalse otherwise.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.delete(key) - removes the value by the key.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.clear() - removes everything from the map.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.size - returns the current element count.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run let map = new Map();

    undefinedundefined

    map.set(‘1', ‘str1'); // a string key map.set(1, ‘num1'); // a numeric key map.set(true, ‘bool1'); // a boolean key

    undefinedundefined

    // remember the regular Object? it would convert keys to string // Map keeps the type, so these two are different: alert( map.get(1) ); // ‘num1' alert( map.get(‘1') ); // ‘str1'

    undefinedundefined

    alert( map.size ); // 3

    undefinedundefined

    As we can see, unlike objects, keys are not converted to strings. Any type of key is possible.

    undefinedundefined

    ``undefinedundefinedsmart header="map[key]undefinedundefinedisn't the right way to use aMapundefinedundefined" Althoughmap[key]undefinedundefinedalso works, e.g. we can setmap[key] = 2undefinedundefined, this is treatingmap` as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on).undefinedundefined

    undefinedundefined

    So we should use undefinedundefinedmap methods: undefinedundefinedset, undefinedundefinedget and so on. undefinedundefined

    undefinedundefined

    undefinedundefinedMap can also use objects as keys.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let john = { name: "John" };

    undefinedundefined

    // for every user, let's store their visits count let visitsCountMap = new Map();

    undefinedundefined

    // john is the key for the map visitsCountMap.set(john, 123);

    undefinedundefined

    alert( visitsCountMap.get(john) ); // 123

    undefinedundefined

    Using objects as keys is one of the most notable and important undefinedundefinedMap features. The same does not count for undefinedundefinedObject. String as a key in undefinedundefinedObject is fine, but we can't use another undefinedundefinedObject as a key in undefinedundefinedObject.undefinedundefined

    undefinedundefined

    Let's try:

    undefinedundefined

    run let john = { name: "John" }; let ben = { name: "Ben" };

    undefinedundefined

    let visitsCountObj = {}; // try to use an object

    undefinedundefined

    visitsCountObj[ben] = 234; // try to use ben object as the key visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced

    undefinedundefined

    undefinedundefined! // That's what got written! alert( visitsCountObj["[object Object]"] ); // 123 undefinedundefined/! undefinedundefined

    undefinedundefined

    As undefinedundefinedvisitsCountObj is an object, it converts all undefinedundefinedObject keys, such as undefinedundefinedjohn and undefinedundefinedben above, to same string undefinedundefined"[object Object]". Definitely not what we want.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="HowMapundefinedundefinedcompares keys" To test keys for equivalence,Mapundefinedundefineduses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality===undefinedundefined, but the difference is thatNaNundefinedundefinedis considered equal toNaNundefinedundefined. SoNaN` can be used as the key as well.undefinedundefined

    undefinedundefined

    This algorithm can't be changed or customized.

    undefinedundefined

    undefinedundefinedsmart header="Chaining" Everymap.set` call returns the map itself, so we can "chain" the calls:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Iteration over Map

    undefinedundefined

    For looping over a undefinedundefinedmap, there are 3 methods:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedmap.keys() - returns an iterable for keys,undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.values() - returns an iterable for values,undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.entries() - returns an iterable for entries undefinedundefined[key, value], it's used by default in undefinedundefinedfor..of.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run let recipeMap = new Map([ [‘cucumber', 500], [‘tomatoes', 350], [‘onion', 50]]);

    undefinedundefined

    // iterate over keys (vegetables) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion }

    undefinedundefined

    // iterate over values (amounts) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 }

    undefinedundefined

    // iterate over [key, value] entries for (let entry of recipeMap) { // the same as of recipeMap.entries() alert(entry); // cucumber,500 (and so on) }

    undefinedundefined

    undefinedundefinedsmart header="The insertion order is used" The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`.undefinedundefined

    undefinedundefined

    Besides that, undefinedundefinedMap has a built-in undefinedundefinedforEach method, similar to undefinedundefinedArray:undefinedundefined

    undefinedundefinedundefinedundefined

    Object.entries: Map from Object

    undefinedundefined

    When a undefinedundefinedMap is created, we can pass an array (or another iterable) with key/value pairs for initialization, like this:undefinedundefined

    undefinedundefined

    run // array of [key, value] pairs let map = new Map([ [‘1', ‘str1'], [1, ‘num1'], [true, ‘bool1']]);

    undefinedundefined

    alert( map.get(‘1') ); // str1

    undefinedundefined

    If we have a plain object, and we'd like to create a undefinedundefinedMap from it, then we can use built-in method undefinedundefinedObject.entries(obj) that returns an array of key/value pairs for an object exactly in that format.undefinedundefined

    undefinedundefined

    So we can create a map from an object like this:

    undefinedundefined

    run let obj = { name: "John", age: 30 };

    undefinedundefined

    undefinedundefined! let map = new Map(Object.entries(obj)); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( map.get(‘name') ); // John

    undefinedundefined

    Here, undefinedundefinedObject.entries returns the array of key/value pairs: undefinedundefined[ ["name","John"], ["age", 30] ]. That's what undefinedundefinedMap needs.undefinedundefined

    undefinedundefined

    Object.fromEntries: Object from Map

    undefinedundefined

    We've just seen how to create undefinedundefinedMap from a plain object with undefinedundefinedObject.entries(obj).undefinedundefined

    undefinedundefined

    There's undefinedundefinedObject.fromEntries method that does the reverse: given an array of undefinedundefined[key, value] pairs, it creates an object from them:undefinedundefined

    undefinedundefined

    run let prices = Object.fromEntries([ [‘banana', 1], [‘orange', 2], [‘meat', 4]]);

    undefinedundefined

    // now prices = { banana: 1, orange: 2, meat: 4 }

    undefinedundefined

    alert(prices.orange); // 2

    undefinedundefined

    We can use undefinedundefinedObject.fromEntries to get a plain object from undefinedundefinedMap.undefinedundefined

    undefinedundefined

    E.g. we store the data in a undefinedundefinedMap, but we need to pass it to a 3rd-party code that expects a plain object.undefinedundefined

    undefinedundefined

    Here we go:

    undefinedundefined

    run let map = new Map(); map.set(‘banana', 1); map.set(‘orange', 2); map.set(‘meat', 4);

    undefinedundefined

    undefinedundefined! let obj = Object.fromEntries(map.entries()); // make a plain object (undefinedundefined) /!*undefinedundefined

    undefinedundefined

    // done! // obj = { banana: 1, orange: 2, meat: 4 }

    undefinedundefined

    alert(obj.orange); // 2

    undefinedundefined

    A call to undefinedundefinedmap.entries() returns an iterable of key/value pairs, exactly in the right format for undefinedundefinedObject.fromEntries.undefinedundefined

    undefinedundefined

    We could also make line undefinedundefined(*) shorter:undefinedundefined

    undefinedundefinedundefinedundefined

    That's the same, because undefinedundefinedObject.fromEntries expects an iterable object as the argument. Not necessarily an array. And the standard iteration for undefinedundefinedmap returns same key/value pairs as undefinedundefinedmap.entries(). So we get a plain object with same key/values as the undefinedundefinedmap.undefinedundefined

    undefinedundefined

    Set

    undefinedundefined

    A undefinedundefinedSet is a special type collection - "set of values" (without keys), where each value may occur only once.undefinedundefined

    undefinedundefined

    Its main methods are:

    undefinedundefined
      undefinedundefined
    • undefinedundefinednew Set(iterable) - creates the set, and if an undefinedundefinediterable object is provided (usually an array), copies values from it into the set.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.add(value) - adds a value, returns the set itself.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.delete(value) - removes the value, returns undefinedundefinedtrue if undefinedundefinedvalue existed at the moment of the call, otherwise undefinedundefinedfalse.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.has(value) - returns undefinedundefinedtrue if the value exists in the set, otherwise undefinedundefinedfalse.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.clear() - removes everything from the set.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.size - is the elements count.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The main feature is that repeated calls of undefinedundefinedset.add(value) with the same value don't do anything. That's the reason why each value appears in a undefinedundefinedSet only once.undefinedundefined

    undefinedundefined

    For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once.

    undefinedundefined

    undefinedundefinedSet is just the right thing for that:undefinedundefined

    undefinedundefined

    run let set = new Set();

    undefinedundefined

    let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" };

    undefinedundefined

    // visits, some users come multiple times set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary);

    undefinedundefined

    // set keeps only unique values alert( set.size ); // 3

    undefinedundefined

    for (let user of set) { alert(user.name); // John (then Pete and Mary) }

    undefinedundefined

    The alternative to undefinedundefinedSet could be an array of users, and the code to check for duplicates on every insertion using undefinedundefinedarr.find. But the performance would be much worse, because this method walks through the whole array checking every element. undefinedundefinedSet is much better optimized internally for uniqueness checks.undefinedundefined

    undefinedundefined

    Iteration over Set

    undefinedundefined

    We can loop over a set either with undefinedundefinedfor..of or using undefinedundefinedforEach:undefinedundefined

    undefinedundefined

    run let set = new Set(["oranges", "apples", "bananas"]);

    undefinedundefined

    for (let value of set) alert(value);

    undefinedundefined

    // the same with forEach: set.forEach((value, valueAgain, set) => { alert(value); });

    undefinedundefined

    Note the funny thing. The callback function passed in undefinedundefinedforEach has 3 arguments: a undefinedundefinedvalue, then undefinedundefinedthe same valueundefinedundefinedvalueAgain, and then the target object. Indeed, the same value appears in the arguments twice.undefinedundefined

    undefinedundefined

    That's for compatibility with undefinedundefinedMap where the callback passed undefinedundefinedforEach has three arguments. Looks a bit strange, for sure. But may help to replace undefinedundefinedMap with undefinedundefinedSet in certain cases with ease, and vice versa.undefinedundefined

    undefinedundefined

    The same methods undefinedundefinedMap has for iterators are also supported:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedset.keys() - returns an iterable object for values,undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.values() - same as undefinedundefinedset.keys(), for compatibility with undefinedundefinedMap,undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.entries() - returns an iterable object for entries undefinedundefined[value, value], exists for compatibility with undefinedundefinedMap.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedMap - is a collection of keyed values.undefinedundefined

    undefinedundefined

    Methods and properties:

    undefinedundefined
      undefinedundefined
    • undefinedundefinednew Map([iterable]) - creates the map, with optional undefinedundefinediterable (e.g. array) of undefinedundefined[key,value] pairs for initialization.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.set(key, value) - stores the value by the key, returns the map itself.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.get(key) - returns the value by the key, undefinedundefinedundefined if undefinedundefinedkey doesn't exist in map.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.has(key) - returns undefinedundefinedtrue if the undefinedundefinedkey exists, undefinedundefinedfalse otherwise.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.delete(key) - removes the value by the key, returns undefinedundefinedtrue if undefinedundefinedkey existed at the moment of the call, otherwise undefinedundefinedfalse.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.clear() - removes everything from the map.undefinedundefined
    • undefinedundefined
    • undefinedundefinedmap.size - returns the current element count.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The differences from a regular undefinedundefinedObject:undefinedundefined

    undefinedundefined
      undefinedundefined
    • Any keys, objects can be keys.
    • undefinedundefined
    • Additional convenient methods, the undefinedundefinedsize property.undefinedundefined
    • undefinedundefined
    undefinedundefined

    undefinedundefinedSet - is a collection of unique values.undefinedundefined

    undefinedundefined

    Methods and properties:

    undefinedundefined
      undefinedundefined
    • undefinedundefinednew Set([iterable]) - creates the set, with optional undefinedundefinediterable (e.g. array) of values for initialization.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.add(value) - adds a value (does nothing if undefinedundefinedvalue exists), returns the set itself.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.delete(value) - removes the value, returns undefinedundefinedtrue if undefinedundefinedvalue existed at the moment of the call, otherwise undefinedundefinedfalse.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.has(value) - returns undefinedundefinedtrue if the value exists in the set, otherwise undefinedundefinedfalse.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.clear() - removes everything from the set.undefinedundefined
    • undefinedundefined
    • undefinedundefinedset.size - is the elements count.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Iteration over undefinedundefinedMap and undefinedundefinedSet is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number.undefinedundefined

    undefinedundefined

    WeakMap and WeakSet

    undefinedundefined

    As we know from the chapter undefinedundefinedinfo:garbage-collection, JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory.

    undefinedundefined

    For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it.

    undefinedundefined

    Like this:

    undefinedundefinedundefinedundefined

    Similar to that, if we use an object as the key in a regular undefinedundefinedMap, then while the undefinedundefinedMap exists, that object exists as well. It occupies memory and may not be garbage collected.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    undefinedundefinedWeakMap is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects.undefinedundefined

    undefinedundefined

    Let's see what it means on examples.

    undefinedundefined

    WeakMap

    undefinedundefined

    The first difference between undefinedundefinedMap and undefinedundefinedWeakMap is that keys must be objects, not primitive values:undefinedundefined

    undefinedundefined

    run let weakMap = new WeakMap();

    undefinedundefined

    let obj = {};

    undefinedundefined

    weakMap.set(obj, "ok"); // works fine (object key)

    undefinedundefined

    undefinedundefined! // can't use a string as the key weakMap.set("test", "Whoops"); // Error, because "test" is not an object undefinedundefined/! undefinedundefined

    undefinedundefined

    Now, if we use an object as the key in it, and there are no other references to that object - it will be removed from memory (and from the map) automatically.

    undefinedundefinedundefinedundefined

    Compare it with the regular undefinedundefinedMap example above. Now if undefinedundefinedjohn only exists as the key of undefinedundefinedWeakMap - it will be automatically deleted from the map (and memory).undefinedundefined

    undefinedundefined

    undefinedundefinedWeakMap does not support iteration and methods undefinedundefinedkeys(), undefinedundefinedvalues(), undefinedundefinedentries(), so there's no way to get all keys or values from it.undefinedundefined

    undefinedundefined

    undefinedundefinedWeakMap has only the following methods:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedweakMap.get(key)undefinedundefined
    • undefinedundefined
    • undefinedundefinedweakMap.set(key, value)undefinedundefined
    • undefinedundefined
    • undefinedundefinedweakMap.delete(key)undefinedundefined
    • undefinedundefined
    • undefinedundefinedweakMap.has(key)undefinedundefined
    • undefinedundefined
    undefinedundefined

    Why such a limitation? That's for technical reasons. If an object has lost all other references (like undefinedundefinedjohn in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified undefinedundefinedwhen the cleanup happens.undefinedundefined

    undefinedundefined

    The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically, the current element count of a undefinedundefinedWeakMap is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported.undefinedundefined

    undefinedundefined

    Now, where do we need such a data structure?

    undefinedundefined

    Use case: additional data

    undefinedundefined

    The main area of application for undefinedundefinedWeakMap is an undefinedundefinedadditional data storage.undefinedundefined

    undefinedundefined

    If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then undefinedundefinedWeakMap is exactly what's needed.undefinedundefined

    undefinedundefined

    We put the data to a undefinedundefinedWeakMap, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well.undefinedundefined

    undefinedundefinedundefinedundefined

    Let's look at an example.

    undefinedundefined

    For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don't want to store their visit count anymore.

    undefinedundefined

    Here's an example of a counting function with undefinedundefinedMap:undefinedundefined

    undefinedundefinedundefinedundefined

    And here's another part of the code, maybe another file using it:

    undefinedundefinedundefinedundefined

    Now, undefinedundefinedjohn object should be garbage collected, but remains in memory, as it's a key in undefinedundefinedvisitsCountMap.undefinedundefined

    undefinedundefined

    We need to clean undefinedundefinedvisitsCountMap when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures.undefinedundefined

    undefinedundefined

    We can avoid it by switching to undefinedundefinedWeakMap instead:undefinedundefined

    undefinedundefinedundefinedundefined

    Now we don't have to clean undefinedundefinedvisitsCountMap. After undefinedundefinedjohn object becomes unreachable, by all means except as a key of undefinedundefinedWeakMap, it gets removed from memory, along with the information by that key from undefinedundefinedWeakMap.undefinedundefined

    undefinedundefined

    Use case: caching

    undefinedundefined

    Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it.

    undefinedundefined

    To achieve that, we can use undefinedundefinedMap (not optimal scenario):undefinedundefined

    undefinedundefined

    run // 📁 cache.js let cache = new Map();

    undefinedundefined

    // calculate and remember the result function process(obj) { if (!cache.has(obj)) { let result = /* calculations of the result for */ obj;

    undefinedundefined
    undefinedundefinedcache.set(obj, result);undefinedundefined
    undefinedundefined

    }

    undefinedundefined

    return cache.get(obj); }

    undefinedundefined

    undefinedundefined! // Now we use process() in another file: undefinedundefined/!undefinedundefined

    undefinedundefined

    // 📁 main.js let obj = {/* let's say we have an object */};

    undefinedundefined

    let result1 = process(obj); // calculated

    undefinedundefined

    // …later, from another place of the code… let result2 = process(obj); // remembered result taken from cache

    undefinedundefined

    // …later, when the object is not needed any more: obj = null;

    undefinedundefined

    alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)

    undefinedundefined

    For multiple calls of undefinedundefinedprocess(obj) with the same object, it only calculates the result the first time, and then just takes it from undefinedundefinedcache. The downside is that we need to clean undefinedundefinedcache when the object is not needed any more.undefinedundefined

    undefinedundefined

    If we replace undefinedundefinedMap with undefinedundefinedWeakMap, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected.undefinedundefined

    undefinedundefined

    run // 📁 cache.js undefinedundefined! let cache = new WeakMap(); undefinedundefined/!undefinedundefined

    undefinedundefined

    // calculate and remember the result function process(obj) { if (!cache.has(obj)) { let result = /* calculate the result for */ obj;

    undefinedundefined
    undefinedundefinedcache.set(obj, result);undefinedundefined
    undefinedundefined

    }

    undefinedundefined

    return cache.get(obj); }

    undefinedundefined

    // 📁 main.js let obj = {/* some object */};

    undefinedundefined

    let result1 = process(obj); let result2 = process(obj);

    undefinedundefined

    // …later, when the object is not needed any more: obj = null;

    undefinedundefined

    // Can't get cache.size, as it's a WeakMap, // but it's 0 or soon be 0 // When obj gets garbage collected, cached data will be removed as well

    undefinedundefined

    WeakSet

    undefinedundefined

    undefinedundefinedWeakSet behaves similarly:undefinedundefined

    undefinedundefined
      undefinedundefined
    • It is analogous to undefinedundefinedSet, but we may only add objects to undefinedundefinedWeakSet (not primitives).undefinedundefined
    • undefinedundefined
    • An object exists in the set while it is reachable from somewhere else.
    • undefinedundefined
    • Like undefinedundefinedSet, it supports undefinedundefinedadd, undefinedundefinedhas and undefinedundefineddelete, but not undefinedundefinedsize, undefinedundefinedkeys() and no iterations.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in undefinedundefinedWeakSet may mean something about the object.undefinedundefined

    undefinedundefined

    For instance, we can add users to undefinedundefinedWeakSet to keep track of those who visited our site:undefinedundefined

    undefinedundefined

    run let visitedSet = new WeakSet();

    undefinedundefined

    let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" };

    undefinedundefined

    visitedSet.add(john); // John visited us visitedSet.add(pete); // Then Pete visitedSet.add(john); // John again

    undefinedundefined

    // visitedSet has 2 users now

    undefinedundefined

    // check if John visited? alert(visitedSet.has(john)); // true

    undefinedundefined

    // check if Mary visited? alert(visitedSet.has(mary)); // false

    undefinedundefined

    john = null;

    undefinedundefined

    // visitedSet will be cleaned automatically

    undefinedundefined

    The most notable limitation of undefinedundefinedWeakMap and undefinedundefinedWeakSet is the absence of iterations, and the inability to get all current content. That may appear inconvenient, but does not prevent undefinedundefinedWeakMap/WeakSet from doing their main job - be an "additional" storage of data for objects which are stored/managed at another place.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedWeakMap is undefinedundefinedMap-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means.undefinedundefined

    undefinedundefined

    undefinedundefinedWeakSet is undefinedundefinedSet-like collection that stores only objects and removes them once they become inaccessible by other means.undefinedundefined

    undefinedundefined

    Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector.

    undefinedundefined

    That comes at the cost of not having support for undefinedundefinedclear, undefinedundefinedsize, undefinedundefinedkeys, undefinedundefinedvalues…undefinedundefined

    undefinedundefined

    undefinedundefinedWeakMap and undefinedundefinedWeakSet are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of undefinedundefinedWeakMap or in a undefinedundefinedWeakSet, it will be cleaned up automatically.undefinedundefined

    undefinedundefined

    Object.keys, values, entries

    undefinedundefined

    Let's step away from the individual data structures and talk about the iterations over them.

    undefinedundefined

    In the previous chapter we saw methods undefinedundefinedmap.keys(), undefinedundefinedmap.values(), undefinedundefinedmap.entries().undefinedundefined

    undefinedundefined

    These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too.

    undefinedundefined

    They are supported for:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedMapundefinedundefined
    • undefinedundefined
    • undefinedundefinedSetundefinedundefined
    • undefinedundefined
    • undefinedundefinedArrayundefinedundefined
    • undefinedundefined
    undefinedundefined

    Plain objects also support similar methods, but the syntax is a bit different.

    undefinedundefined

    Object.keys, values, entries

    undefinedundefined

    For plain objects, the following methods are available:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedObject.keys(obj) - returns an array of keys.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.values(obj) - returns an array of values.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.entries(obj) - returns an array of undefinedundefined[key, value] pairs.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Please note the distinctions (compared to map for example):

    undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined
    MapObject
    Call syntax undefinedundefinedmap.keys()undefinedundefined undefinedundefinedObject.keys(obj), but not undefinedundefinedobj.keys()undefinedundefined
    Returnsiterable "real" Array
    undefinedundefined

    The first difference is that we have to call undefinedundefinedObject.keys(obj), and not undefinedundefinedobj.keys().undefinedundefined

    undefinedundefined

    Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like undefinedundefineddata that implements its own undefinedundefineddata.values() method. And we still can call undefinedundefinedObject.values(data) on it.undefinedundefined

    undefinedundefined

    The second difference is that undefinedundefinedObject.* methods return "real" array objects, not just an iterable. That's mainly for historical reasons.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined
      undefinedundefined
    • undefinedundefinedObject.keys(user) = ["name", "age"]undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.values(user) = ["John", 30]undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.entries(user) = [ ["name","John"], ["age",30] ]undefinedundefined
    • undefinedundefined
    undefinedundefined

    Here's an example of using undefinedundefinedObject.values to loop over property values:undefinedundefined

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    // loop over values for (let value of Object.values(user)) { alert(value); // John, then 30 }

    undefinedundefined

    ``undefinedundefinedwarn header="Object.keys/values/entries ignore symbolic properties" Just like afor..inundefinedundefinedloop, these methods ignore properties that useSymbol(…)` as keys.undefinedundefined

    undefinedundefined

    Usually that's convenient. But if we want symbolic keys too, then there's a separate method undefinedundefinedObject.getOwnPropertySymbols that returns an array of only symbolic keys. Also, there exist a method undefinedundefinedReflect.ownKeys(obj) that returns undefinedundefinedall keys. undefinedundefined

    undefinedundefined

    Transforming objects

    undefinedundefined

    Objects lack many methods that exist for arrays, e.g. undefinedundefinedmap, undefinedundefinedfilter and others.undefinedundefined

    undefinedundefined

    If we'd like to apply them, then we can use undefinedundefinedObject.entries followed by undefinedundefinedObject.fromEntries:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Use undefinedundefinedObject.entries(obj) to get an array of key/value pairs from undefinedundefinedobj.undefinedundefined
    2. undefinedundefined
    3. Use array methods on that array, e.g. undefinedundefinedmap.undefinedundefined
    4. undefinedundefined
    5. Use undefinedundefinedObject.fromEntries(array) on the resulting array to turn it back into an object.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    For example, we have an object with prices, and would like to double them:

    undefinedundefined

    run let prices = { banana: 1, orange: 2, meat: 4, };

    undefinedundefined

    undefinedundefined! let doublePrices = Object.fromEntries( // convert to array, map, and then fromEntries gives back the object Object.entries(prices).map(([key, value]) => [key, value * 2]) ); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(doublePrices.meat); // 8

    undefinedundefined

    It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way.

    undefinedundefined

    Destructuring assignment

    undefinedundefined

    The two most used data structures in JavaScript are undefinedundefinedObject and undefinedundefinedArray.undefinedundefined

    undefinedundefined
      undefinedundefined
    • Objects allow us to create a single entity that stores data items by key.
    • undefinedundefined
    • Arrays allow us to gather data items into an ordered list.
    • undefinedundefined
    undefinedundefined

    Although, when we pass those to a function, it may need not an object/array as a whole. It may need individual pieces.

    undefinedundefined

    undefinedundefinedDestructuring assignment is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient.undefinedundefined

    undefinedundefined

    Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that.

    undefinedundefined

    Array destructuring

    undefinedundefined

    Here's an example of how an array is destructured into variables:

    undefinedundefined undefinedundefined

    Now we can work with variables instead of array members.

    undefinedundefined

    It looks great when combined with undefinedundefinedsplit or other array-returning methods:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let [firstName, surname] = "John Smith".split(' '); alert(firstName); // John alert(surname); // Smithundefinedundefined

    undefinedundefined

    As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples, to better understand it.

    undefinedundefined

    smart header=""Destructuring" does not mean "destructive"." It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified.

    undefinedundefined

    It's just a shorter way to write:

    undefinedundefinedundefinedundefined

    undefinedundefined

    smart header="Ignore elements using commas" Unwanted elements of the array can also be thrown away via an extra comma:

    undefinedundefined

    run undefinedundefined! // second element is not needed let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( title ); // Consul

    undefinedundefined
    undefinedundefined
    In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them).undefinedundefined
    undefinedundefined

    smart header="Works with any iterable on the right-side"

    undefinedundefined

    …Actually, we can use it with any iterable, not only arrays:

    undefinedundefinedundefinedundefined

    That works, because internally a destructuring assignment works by iterating over the right value. It's kind of syntax sugar for calling undefinedundefinedfor..of over the value to the right of undefinedundefined= and assigning the values. undefinedundefined

    undefinedundefined

    smart header="Assign to anything at the left-side" We can use any "assignables" at the left side.

    undefinedundefined

    For instance, an object property: run let user = {}; [user.name, user.surname] = "John Smith".split(''' ''');

    undefinedundefined

    alert(user.name); // John alert(user.surname); // Smith

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    smart header="Looping with .entries()" In the previous chapter we saw the undefinedundefinedObject.entries(obj) method.undefinedundefined

    undefinedundefined

    We can use it with destructuring to loop over keys-and-values of an object:

    undefinedundefined

    run let user = { name: "John", age: 30 };

    undefinedundefined

    // loop over keys-and-values undefinedundefined! for (let [key, value] of Object.entries(user)) { undefinedundefined/! alert(undefinedundefined${key}:${value}); // name:John, then age:30 } undefinedundefined

    undefinedundefined

    The similar code for a undefinedundefinedMap is simpler, as it's iterable:undefinedundefined

    undefinedundefined

    run let user = new Map(); user.set("name", "John"); user.set("age", "30");

    undefinedundefined

    undefinedundefined! // Map iterates as [key, value] pairs, very convenient for destructuring for (let [key, value] of user) { undefinedundefined/! alert(undefinedundefined${key}:${value}); // name:John, then age:30 }undefinedundefined

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    smart header="Swap variables trick" There's a well-known trick for swapping values of two variables using a destructuring assignment:

    undefinedundefined

    run let guest = "Jane"; let admin = "Pete";

    undefinedundefined

    // Let's swap the values: make guest=Pete, admin=Jane undefinedundefined! [guest, admin] = [admin, guest]; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(undefinedundefined${guest} ${admin}); // Pete Jane (successfully swapped!)undefinedundefined

    undefinedundefined
    undefinedundefined
    Here we create a temporary array of two variables and immediately destructure it in swapped order.
    We can swap more than two variables this way.undefinedundefined
    undefinedundefined

    The rest ‘…'

    undefinedundefined

    Usually, if the array is longer than the list at the left, the "extra" items are omitted.

    undefinedundefined

    For example, here only two items are taken, and the rest is just ignored:

    undefinedundefined

    run let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

    undefinedundefined

    alert(name1); // Julius alert(name2); // Caesar // Further items aren't assigned anywhere

    undefinedundefined

    If we'd like also to gather all that follows - we can add one more parameter that gets "the rest" using three dots undefinedundefined"...":undefinedundefined

    undefinedundefined

    run let [name1, name2, undefinedundefined!…restundefinedundefined/!] = ["Julius", "Caesar", undefinedundefined!"Consul", "of the Roman Republic"undefinedundefined/!];undefinedundefined

    undefinedundefined

    undefinedundefined! // rest is array of items, starting from the 3rd one alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 undefinedundefined/! undefinedundefined

    undefinedundefined

    The value of undefinedundefinedrest is the array of the remaining array elements.undefinedundefined

    undefinedundefined

    We can use any other variable name in place of undefinedundefinedrest, just make sure it has three dots before it and goes last in the destructuring assignment.undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; // now titles = ["Consul", "of the Roman Republic"]undefinedundefined

    undefinedundefined

    Default values

    undefinedundefined

    If the array is shorter than the list of variables at the left, there'll be no errors. Absent values are considered undefined:

    undefinedundefined

    run undefinedundefined! let [firstName, surname] = []; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(firstName); // undefined alert(surname); // undefined

    undefinedundefined

    If we want a "default" value to replace the missing one, we can provide it using undefinedundefined=:undefinedundefined

    undefinedundefined

    run undefinedundefined! // default values let [name = "Guest", surname = "Anonymous"] = ["Julius"]; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(name); // Julius (from array) alert(surname); // Anonymous (default used)

    undefinedundefined

    Default values can be more complex expressions or even function calls. They are evaluated only if the value is not provided.

    undefinedundefined

    For instance, here we use the undefinedundefinedprompt function for two defaults:undefinedundefined

    undefinedundefined

    run // runs only prompt for surname let [name = prompt(‘name?'''), surname = prompt(‘surname?''')] = ["Julius"];

    undefinedundefined

    alert(name); // Julius (from array) alert(surname); // whatever prompt gets

    undefinedundefined

    Please note: the undefinedundefinedprompt will run only for the missing value (undefinedundefinedsurname).undefinedundefined

    undefinedundefined

    Object destructuring

    undefinedundefined

    The destructuring assignment also works with objects.

    undefinedundefined

    The basic syntax is:

    undefinedundefinedundefinedundefined

    We should have an existing object at the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in undefinedundefined{...}.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let options = { title: "Menu", width: 100, height: 200 };

    undefinedundefined

    undefinedundefined! let {title, width, height} = options; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(title); // Menu alert(width); // 100 alert(height); // 200

    undefinedundefined

    Properties undefinedundefinedoptions.title, undefinedundefinedoptions.width and undefinedundefinedoptions.height are assigned to the corresponding variables.undefinedundefined

    undefinedundefined

    The order does not matter. This works too:

    undefinedundefinedundefinedundefined

    The pattern on the left side may be more complex and specify the mapping between properties and variables.

    undefinedundefined

    If we want to assign a property to a variable with another name, for instance, make undefinedundefinedoptions.width go into the variable named undefinedundefinedw, then we can set the variable name using a colon:undefinedundefined

    undefinedundefined

    run let options = { title: "Menu", width: 100, height: 200 };

    undefinedundefined

    undefinedundefined! // { sourceProperty: targetVariable } let {width: w, height: h, title} = options; undefinedundefined/!undefinedundefined

    undefinedundefined

    // width -> w // height -> h // title -> title

    undefinedundefined

    alert(title); // Menu alert(w); // 100 alert(h); // 200

    undefinedundefined

    The colon shows "what : goes where". In the example above the property undefinedundefinedwidth goes to undefinedundefinedw, property undefinedundefinedheight goes to undefinedundefinedh, and undefinedundefinedtitle is assigned to the same name.undefinedundefined

    undefinedundefined

    For potentially missing properties we can set default values using undefinedundefined"=", like this:undefinedundefined

    undefinedundefined

    run let options = { title: "Menu" };

    undefinedundefined

    undefinedundefined! let {width = 100, height = 200, title} = options; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(title); // Menu alert(width); // 100 alert(height); // 200

    undefinedundefined

    Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided.

    undefinedundefined

    In the code below undefinedundefinedprompt asks for undefinedundefinedwidth, but not for undefinedundefinedtitle:undefinedundefined

    undefinedundefined

    run let options = { title: "Menu" };

    undefinedundefined

    undefinedundefined! let {width = prompt("width?"), title = prompt("title?")} = options; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(title); // Menu alert(width); // (whatever the result of prompt is)

    undefinedundefined

    We also can combine both the colon and equality:

    undefinedundefined

    run let options = { title: "Menu" };

    undefinedundefined

    undefinedundefined! let {width: w = 100, height: h = 200, title} = options; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(title); // Menu alert(w); // 100 alert(h); // 200

    undefinedundefined

    If we have a complex object with many properties, we can extract only what we need:

    undefinedundefined

    run let options = { title: "Menu", width: 100, height: 200 };

    undefinedundefined

    // only extract title as a variable let { title } = options;

    undefinedundefined

    alert(title); // Menu

    undefinedundefined

    The rest pattern "…"

    undefinedundefined

    What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere?

    undefinedundefined

    We can use the rest pattern, just like we did with arrays. It's not supported by some older browsers (IE, use Babel to polyfill it), but works in modern ones.

    undefinedundefined

    It looks like this:

    undefinedundefined

    run let options = { title: "Menu", height: 200, width: 100 };

    undefinedundefined

    undefinedundefined! // title = property named title // rest = object with the rest of properties let {title, …rest} = options; undefinedundefined/!undefinedundefined

    undefinedundefined

    // now title="Menu", rest={height: 200, width: 100} alert(rest.height); // 200 alert(rest.width); // 100

    undefinedundefined

    undefinedundefinedsmart header="Gotcha if there's noletundefinedundefined" In the examples above variables were declared right in the assignment:let {…} = {…}undefinedundefined. Of course, we could use existing variables too, withoutlet`. But there's a catch.undefinedundefined

    undefinedundefined

    This won't work: run let title, width, height;

    undefinedundefined

    // error in this line {title, width, height} = {title: "Menu", width: 200, height: 100};

    undefinedundefined

    The problem is that JavaScript treats undefinedundefined{...} in the main code flow (not inside another expression) as a code block. Such code blocks can be used to group statements, like this:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run { // a code block let message = "Hello"; // ... alert( message ); }undefinedundefined

    undefinedundefined

    So here JavaScript assumes that we have a code block, that's why there's an error. We want destructuring instead.

    undefinedundefined

    To show JavaScript that it's not a code block, we can wrap the expression in parentheses undefinedundefined(...):undefinedundefined

    undefinedundefined

    run let title, width, height;

    undefinedundefined

    // okay now undefinedundefined!(undefinedundefined/!{title, width, height} = {title: "Menu", width: 200, height: 100}undefinedundefined!)undefinedundefined/!;undefinedundefined

    undefinedundefined

    alert( title ); // Menu

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    Nested destructuring

    undefinedundefined

    If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions.

    undefinedundefined

    In the code below undefinedundefinedoptions has another object in the property undefinedundefinedsize and an array in the property undefinedundefineditems. The pattern at the left side of the assignment has the same structure to extract values from them:undefinedundefined

    undefinedundefined

    run let options = { size: { width: 100, height: 200 }, items: ["Cake", "Donut"], extra: trueundefinedundefined
    };undefinedundefined

    undefinedundefined

    // destructuring assignment split in multiple lines for clarity let { size: { // put size here width, height }, items: [item1, item2], // assign items here title = "Menu" // not present in the object (default value is used) } = options;

    undefinedundefined

    alert(title); // Menu alert(width); // 100 alert(height); // 200 alert(item1); // Cake alert(item2); // Donut

    undefinedundefined

    All properties of undefinedundefinedoptions object except undefinedundefinedextra that is absent in the left part, are assigned to corresponding variables:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Finally, we have undefinedundefinedwidth, undefinedundefinedheight, undefinedundefineditem1, undefinedundefineditem2 and undefinedundefinedtitle from the default value.undefinedundefined

    undefinedundefined

    Note that there are no variables for undefinedundefinedsize and undefinedundefineditems, as we take their content instead.undefinedundefined

    undefinedundefined

    Smart function parameters

    undefinedundefined

    There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on.

    undefinedundefined

    Here's a bad way to write such function:

    undefinedundefinedundefinedundefined

    In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still… Another problem is how to call a function when most parameters are ok by default.

    undefinedundefined

    Like this?

    undefinedundefinedundefinedundefined

    That's ugly. And becomes unreadable when we deal with more parameters.

    undefinedundefined

    Destructuring comes to the rescue!

    undefinedundefined

    We can pass parameters as an object, and the function immediately destructurizes them into variables:

    undefinedundefined

    run // we pass object to function let options = { title: "My menu", items: ["Item1", "Item2"] };

    undefinedundefined

    // …and it immediately expands it to variables function showMenu(undefinedundefined!{title = "Untitled", width = 200, height = 100, items = []}undefinedundefined/!) { // title, items - taken from options, // width, height - defaults used alert( undefinedundefined${title} ${width} ${height} ); // My Menu 200 100 alert( items ); // Item1, Item2 }undefinedundefined

    undefinedundefined

    showMenu(options);

    undefinedundefined

    We can also use more complex destructuring with nested objects and colon mappings:

    undefinedundefined

    run let options = { title: "My menu", items: ["Item1", "Item2"] };

    undefinedundefined

    undefinedundefined! function showMenu({ title = "Untitled", width: w = 100, // width goes to w height: h = 200, // height goes to h items: [item1, item2] // items first element goes to item1, second to item2 }) { undefinedundefined/! alert( undefinedundefined${title} ${w} ${h} ); // My Menu 100 200 alert( item1 ); // Item1 alert( item2 ); // Item2 }undefinedundefined

    undefinedundefined

    showMenu(options);

    undefinedundefined

    The full syntax is the same as for a destructuring assignment:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunction(undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedincomingPropertyundefinedundefined: varName undefinedundefined= defaultValueundefinedundefinedundefinedundefined  ...undefinedundefinedundefinedundefined})undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Then, for an object of parameters, there will be a variable undefinedundefinedvarName for property undefinedundefinedincomingProperty, with undefinedundefineddefaultValue by default.undefinedundefined

    undefinedundefined

    Please note that such destructuring assumes that undefinedundefinedshowMenu() does have an argument. If we want all values by default, then we should specify an empty object:undefinedundefined

    undefinedundefinedundefinedundefined

    We can fix this by making undefinedundefined{} the default value for the whole object of parameters:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { alert(${title} ${width} ${height}` ); }undefinedundefined

    undefinedundefined

    showMenu(); // Menu 100 200

    undefinedundefined

    In the code above, the whole arguments object is undefinedundefined{} by default, so there's always something to destructurize.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Destructuring assignment allows for instantly mapping an object or array onto many variables.
    • undefinedundefined
    • undefinedundefined

      The full object syntax: undefinedundefinedjs let {prop : varName = default, ...rest} = objectundefinedundefined

      undefinedundefined

      This means that property undefinedundefinedprop should go into the variable undefinedundefinedvarName and, if no such property exists, then the undefinedundefineddefault value should be used.undefinedundefined

      undefinedundefined

      Object properties that have no mapping are copied to the undefinedundefinedrest object.undefinedundefined

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      The full array syntax:

      undefinedundefinedundefinedundefined

      The first item goes to undefinedundefineditem1; the second goes into undefinedundefineditem2, all the rest makes the array undefinedundefinedrest.undefinedundefined

      undefinedundefined
    • undefinedundefined
    • undefinedundefined

      It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one.

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Date and time

    undefinedundefined

    Let's meet a new built-in object: undefinedundefinedDate. It stores the date, time and provides methods for date/time management.undefinedundefined

    undefinedundefined

    For instance, we can use it to store creation/modification times, to measure time, or just to print out the current date.

    undefinedundefined

    Creation

    undefinedundefined

    To create a new undefinedundefinedDate object call undefinedundefinednew Date() with one of the following arguments:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinednew Date()undefinedundefined
    undefinedundefined
    undefinedundefined

    Without arguments - create a undefinedundefinedDate object for the current date and time:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let now = new Date(); alert( now ); // shows current date/timeundefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinednew Date(milliseconds)undefinedundefined
    undefinedundefined
    undefinedundefined

    Create a undefinedundefinedDate object with the time equal to number of milliseconds (1/1000 of a second) passed after the Jan 1st of 1970 UTC+0.undefinedundefined

    undefinedundefined

    run // 0 means 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 );

    undefinedundefined

    // now add 24 hours, get 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 );

    undefinedundefined

    An integer number representing the number of milliseconds that has passed since the beginning of 1970 is called a undefinedundefinedtimestamp.undefinedundefined

    undefinedundefined

    It's a lightweight numeric representation of a date. We can always create a date from a timestamp using undefinedundefinednew Date(timestamp) and convert the existing undefinedundefinedDate object to a timestamp using the undefinedundefineddate.getTime() method (see below).undefinedundefined

    undefinedundefined

    Dates before 01.01.1970 have negative timestamps, e.g.: undefinedundefinedjs run // 31 Dec 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 );undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinednew Date(datestring)undefinedundefined
    undefinedundefined
    undefinedundefined

    If there is a single argument, and it's a string, then it is parsed automatically. The algorithm is the same as undefinedundefinedDate.parse uses, we'll cover it later.undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let date = new Date("2017-01-26"); alert(date); // The time is not set, so it's assumed to be midnight GMT and // is adjusted according to the timezone the code is run in // So the result could be // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) // or // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinednew Date(year, month, date, hours, minutes, seconds, ms)undefinedundefined
    undefinedundefined
    undefinedundefined

    Create the date with the given components in the local time zone. Only the first two arguments are obligatory.

    undefinedundefined
      undefinedundefined
    • The undefinedundefinedyear must have 4 digits: undefinedundefined2013 is okay, undefinedundefined98 is not.undefinedundefined
    • undefinedundefined
    • The undefinedundefinedmonth count starts with undefinedundefined0 (Jan), up to undefinedundefined11 (Dec).undefinedundefined
    • undefinedundefined
    • The undefinedundefineddate parameter is actually the day of month, if absent then undefinedundefined1 is assumed.undefinedundefined
    • undefinedundefined
    • If undefinedundefinedhours/minutes/seconds/ms is absent, they are assumed to be equal undefinedundefined0.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    The maximal precision is 1 ms (1/1000 sec):

    undefinedundefined

    undefinedundefinedjs run let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    Access date components

    undefinedundefined

    There are methods to access the year, month and so on from the undefinedundefinedDate object:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedgetFullYear()undefinedundefined
    undefinedundefined
    Get the year (4 digits)
    undefinedundefined
    undefinedundefinedgetMonth()undefinedundefined
    undefinedundefined
    Get the month, undefinedundefinedfrom 0 to 11. undefinedundefined
    undefinedundefined
    undefinedundefinedgetDate()undefinedundefined
    undefinedundefined
    Get the day of month, from 1 to 31, the name of the method does look a little bit strange.
    undefinedundefined
    undefinedundefinedgetHours(), undefinedundefinedgetMinutes(), undefinedundefinedgetSeconds(), undefinedundefinedgetMilliseconds()undefinedundefined
    undefinedundefined
    Get the corresponding time components.
    undefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="Not `getYear()`, but `getFullYear()`" Many JavaScript engines implement a non-standard method `getYear()`. This method is deprecated. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for the year.undefinedundefined

    undefinedundefined

    Additionally, we can get a day of week:

    undefinedundefined
    undefinedundefined
    undefinedundefinedgetDay()undefinedundefined
    undefinedundefined
    Get the day of week, from undefinedundefined0 (Sunday) to undefinedundefined6 (Saturday). The first day is always Sunday, in some countries that's not so, but can't be changed. undefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefinedAll the methods above return the components relative to the local time zone.undefinedundefined

    undefinedundefined

    There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0: undefinedundefinedgetUTCFullYear(), undefinedundefinedgetUTCMonth(), undefinedundefinedgetUTCDay(). Just insert the undefinedundefined"UTC" right after undefinedundefined"get".undefinedundefined

    undefinedundefined

    If your local time zone is shifted relative to UTC, then the code below shows different hours:

    undefinedundefined

    run // current date let date = new Date();

    undefinedundefined

    // the hour in your current time zone alert( date.getHours() );

    undefinedundefined

    // the hour in UTC+0 time zone (London time without daylight savings) alert( date.getUTCHours() );

    undefinedundefined

    Besides the given methods, there are two special ones that do not have a UTC-variant:

    undefinedundefined
    undefinedundefined
    undefinedundefinedgetTime()undefinedundefined
    undefinedundefined
    Returns the timestamp for the date - a number of milliseconds passed from the January 1st of 1970 UTC+0.
    undefinedundefined
    undefinedundefinedgetTimezoneOffset()undefinedundefined
    undefinedundefined
    undefinedundefined

    Returns the difference between UTC and the local time zone, in minutes:

    undefinedundefined

    run // if you are in timezone UTC-1, outputs 60 // if you are in timezone UTC+3, outputs -180 alert( new Date().getTimezoneOffset() );

    undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    Setting date components

    undefinedundefined

    The following methods allow to set date/time components:

    undefinedundefined undefinedundefined

    Every one of them except undefinedundefinedsetTime() has a UTC-variant, for instance: undefinedundefinedsetUTCHours().undefinedundefined

    undefinedundefined

    As we can see, some methods can set multiple components at once, for example undefinedundefinedsetHours. The components that are not mentioned are not modified.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let today = new Date();

    undefinedundefined

    today.setHours(0); alert(today); // still today, but the hour is changed to 0

    undefinedundefined

    today.setHours(0, 0, 0, 0); alert(today); // still today, now 00:00:00 sharp.

    undefinedundefined

    Autocorrection

    undefinedundefined

    The undefinedundefinedautocorrection is a very handy feature of undefinedundefinedDate objects. We can set out-of-range values, and it will auto-adjust itself.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? alert(date); // ...is 1st Feb 2013!undefinedundefined

    undefinedundefined

    Out-of-range date components are distributed automatically.

    undefinedundefined

    Let's say we need to increase the date "28 Feb 2016" by 2 days. It may be "2 Mar" or "1 Mar" in case of a leap-year. We don't need to think about it. Just add 2 days. The undefinedundefinedDate object will do the rest:undefinedundefined

    undefinedundefined

    run let date = new Date(2016, 1, 28); undefinedundefined! date.setDate(date.getDate() + 2); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( date ); // 1 Mar 2016

    undefinedundefined

    That feature is often used to get the date after the given period of time. For instance, let's get the date for "70 seconds after now":

    undefinedundefined

    run let date = new Date(); date.setSeconds(date.getSeconds() + 70);

    undefinedundefined

    alert( date ); // shows the correct date

    undefinedundefined

    We can also set zero or even negative values. For example:

    undefinedundefined

    run let date = new Date(2016, 0, 2); // 2 Jan 2016

    undefinedundefined

    date.setDate(1); // set day 1 of month alert( date );

    undefinedundefined

    date.setDate(0); // min day is 1, so the last day of the previous month is assumed alert( date ); // 31 Dec 2015

    undefinedundefined

    Date to number, date diff

    undefinedundefined

    When a undefinedundefinedDate object is converted to number, it becomes the timestamp same as undefinedundefineddate.getTime():undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let date = new Date(); alert(+date); // the number of milliseconds, same as date.getTime()undefinedundefined

    undefinedundefined

    The important side effect: dates can be subtracted, the result is their difference in ms.

    undefinedundefined

    That can be used for time measurements:

    undefinedundefined

    run let start = new Date(); // start measuring time

    undefinedundefined

    // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; }

    undefinedundefined

    let end = new Date(); // end measuring time

    undefinedundefined

    alert( undefinedundefinedThe loop took ${end - start} ms ); undefinedundefined

    undefinedundefined

    Date.now()

    undefinedundefined

    If we only want to measure time, we don't need the undefinedundefinedDate object.undefinedundefined

    undefinedundefined

    There's a special method undefinedundefinedDate.now() that returns the current timestamp.undefinedundefined

    undefinedundefined

    It is semantically equivalent to undefinedundefinednew Date().getTime(), but it doesn't create an intermediate undefinedundefinedDate object. So it's faster and doesn't put pressure on garbage collection.undefinedundefined

    undefinedundefined

    It is used mostly for convenience or when performance matters, like in games in JavaScript or other specialized applications.

    undefinedundefined

    So this is probably better:

    undefinedundefined

    run undefinedundefined! let start = Date.now(); // milliseconds count from 1 Jan 1970 undefinedundefined/!undefinedundefined

    undefinedundefined

    // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; }

    undefinedundefined

    undefinedundefined! let end = Date.now(); // done undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( undefinedundefinedThe loop took ${end - start} ms ); // subtract numbers, not dates undefinedundefined

    undefinedundefined

    Benchmarking

    undefinedundefined

    If we want a reliable benchmark of CPU-hungry function, we should be careful.

    undefinedundefined

    For instance, let's measure two functions that calculate the difference between two dates: which one is faster?

    undefinedundefined

    Such performance measurements are often called "benchmarks".

    undefinedundefinedundefinedundefined

    These two do exactly the same thing, but one of them uses an explicit undefinedundefineddate.getTime() to get the date in ms, and the other one relies on a date-to-number transform. Their result is always the same.undefinedundefined

    undefinedundefined

    So, which one is faster?

    undefinedundefined

    The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it at least 100000 times.

    undefinedundefined

    Let's measure:

    undefinedundefined

    run function diffSubtract(date1, date2) { return date2 - date1; }

    undefinedundefined

    function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); }

    undefinedundefined

    function bench(f) { let date1 = new Date(0); let date2 = new Date();

    undefinedundefined

    let start = Date.now(); for (let i = 0; i < 100000; i++) f(date1, date2); return Date.now() - start; }

    undefinedundefined

    alert( ‘Time of diffSubtract:''' + bench(diffSubtract) + ‘ms' ); alert( ‘Time of diffGetTime:''' + bench(diffGetTime) + ‘ms' );

    undefinedundefined

    Wow! Using undefinedundefinedgetTime() is so much faster! That's because there's no type conversion, it is much easier for engines to optimize.undefinedundefined

    undefinedundefined

    Okay, we have something. But that's not a good benchmark yet.

    undefinedundefined

    Imagine that at the time of running undefinedundefinedbench(diffSubtract) CPU was doing something in parallel, and it was taking resources. And by the time of running undefinedundefinedbench(diffGetTime) that work has finished.undefinedundefined

    undefinedundefined

    A pretty real scenario for a modern multi-process OS.

    undefinedundefined

    As a result, the first benchmark will have less CPU resources than the second. That may lead to wrong results.

    undefinedundefined

    undefinedundefinedFor more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.undefinedundefined

    undefinedundefined

    For example, like this:

    undefinedundefined

    run function diffSubtract(date1, date2) { return date2 - date1; }

    undefinedundefined

    function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); }

    undefinedundefined

    function bench(f) { let date1 = new Date(0); let date2 = new Date();

    undefinedundefined

    let start = Date.now(); for (let i = 0; i < 100000; i++) f(date1, date2); return Date.now() - start; }

    undefinedundefined

    let time1 = 0; let time2 = 0;

    undefinedundefined

    undefinedundefined! // run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( ‘Total time for diffSubtract:''' + time1 ); alert( ‘Total time for diffGetTime:''' + time2 );

    undefinedundefined

    Modern JavaScript engines start applying advanced optimizations only to "hot code" that executes many times (no need to optimize rarely executed things). So, in the example above, first executions are not well-optimized. We may want to add a heat-up run:

    undefinedundefinedundefinedundefined

    warn header="Be careful doing microbenchmarking" Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all.

    undefinedundefined

    The great pack of articles about V8 can be found at undefinedundefinedhttp://mrale.ph. undefinedundefined

    undefinedundefined

    Date.parse from a string

    undefinedundefined

    The method undefinedundefinedDate.parse(str) can read a date from a string.undefinedundefined

    undefinedundefined

    The string format should be: undefinedundefinedYYYY-MM-DDTHH:mm:ss.sssZ, where:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedYYYY-MM-DD - is the date: year-month-day.undefinedundefined
    • undefinedundefined
    • The character undefinedundefined"T" is used as the delimiter.undefinedundefined
    • undefinedundefined
    • undefinedundefinedHH:mm:ss.sss - is the time: hours, minutes, seconds and milliseconds.undefinedundefined
    • undefinedundefined
    • The optional undefinedundefined'Z' part denotes the time zone in the format undefinedundefined+-hh:mm. A single letter undefinedundefinedZ would mean UTC+0.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Shorter variants are also possible, like undefinedundefinedYYYY-MM-DD or undefinedundefinedYYYY-MM or even undefinedundefinedYYYY.undefinedundefined

    undefinedundefined

    The call to undefinedundefinedDate.parse(str) parses the string in the given format and returns the timestamp (number of milliseconds from 1 Jan 1970 UTC+0). If the format is invalid, returns undefinedundefinedNaN.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let ms = Date.parse(‘2012-01-26T13:51:50.417-07:00');

    undefinedundefined

    alert(ms); // 1327611110417 (timestamp)

    undefinedundefined

    We can instantly create a undefinedundefinednew Date object from the timestamp:undefinedundefined

    undefinedundefined

    run let date = new Date( Date.parse(‘2012-01-26T13:51:50.417-07:00') );

    undefinedundefined

    alert(date);undefinedundefined
    undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Date and time in JavaScript are represented with the undefinedundefinedDate object. We can't create "only date" or "only time": undefinedundefinedDate objects always carry both.undefinedundefined
    • undefinedundefined
    • Months are counted from zero (yes, January is a zero month).
    • undefinedundefined
    • Days of week in undefinedundefinedgetDay() are also counted from zero (that's Sunday).undefinedundefined
    • undefinedundefined
    • undefinedundefinedDate auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours.undefinedundefined
    • undefinedundefined
    • Dates can be subtracted, giving their difference in milliseconds. That's because a undefinedundefinedDate becomes the timestamp when converted to a number.undefinedundefined
    • undefinedundefined
    • Use undefinedundefinedDate.now() to get the current timestamp fast.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds.

    undefinedundefined

    Sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has undefinedundefinedperformance.now() that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point):undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert(`Loading started ${performance.now()}ms ago`); // Something like: "Loading started 34731.26000000001ms ago" // .26 is microseconds (260 microseconds) // more than 3 digits after the decimal point are precision errors, only the first 3 are correctundefinedundefined

    undefinedundefined

    Node.js has undefinedundefinedmicrotime module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in undefinedundefinedDate.undefinedundefined

    undefinedundefined

    JSON methods, toJSON

    undefinedundefined

    Let's say we have a complex object, and we'd like to convert it into a string, to send it over a network, or just to output it for logging purposes.

    undefinedundefined

    Naturally, such a string should include all important properties.

    undefinedundefined

    We could implement the conversion like this:

    undefinedundefined

    run let user = { name: "John", age: 30,

    undefinedundefined

    undefinedundefined! toString() { return undefinedundefined{name: "${this.name}", age: ${this.age}}; } undefinedundefined/! };undefinedundefined

    undefinedundefined

    alert(user); // {name: "John", age: 30}

    undefinedundefined

    …But in the process of development, new properties are added, old properties are renamed and removed. Updating such undefinedundefinedtoString every time can become a pain. We could try to loop over properties in it, but what if the object is complex and has nested objects in properties? We'd need to implement their conversion as well.undefinedundefined

    undefinedundefined

    Luckily, there's no need to write the code to handle all this. The task has been solved already.

    undefinedundefined

    JSON.stringify

    undefinedundefined

    The undefinedundefinedJSON (JavaScript Object Notation) is a general format to represent values and objects. It is described as in undefinedundefinedRFC 4627 standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever.undefinedundefined

    undefinedundefined

    JavaScript provides methods:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedJSON.stringify to convert objects into JSON.undefinedundefined
    • undefinedundefined
    • undefinedundefinedJSON.parse to convert JSON back into an object.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance, here we undefinedundefinedJSON.stringify a student: run let student = { name: ‘John', age: 30, isAdmin: false, courses: [‘html', ‘css', ‘js'], wife: null };undefinedundefined

    undefinedundefined

    undefinedundefined! let json = JSON.stringify(student); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(typeof json); // we've got a string!

    undefinedundefined

    alert(json); undefinedundefined! /* JSON-encoded object: { "name": "John", "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], "wife": null } undefinedundefined/ /!* undefinedundefined

    undefinedundefined

    The method undefinedundefinedJSON.stringify(student) takes the object and converts it into a string.undefinedundefined

    undefinedundefined

    The resulting undefinedundefinedjson string is called a undefinedundefinedJSON-encoded or undefinedundefinedserialized or undefinedundefinedstringified or undefinedundefinedmarshalled object. We are ready to send it over the wire or put into a plain data store.undefinedundefined

    undefinedundefined

    Please note that a JSON-encoded object has several important differences from the object literal:

    undefinedundefined
      undefinedundefined
    • Strings use double quotes. No single quotes or backticks in JSON. So undefinedundefined'John' becomes undefinedundefined"John".undefinedundefined
    • undefinedundefined
    • Object property names are double-quoted also. That's obligatory. So undefinedundefinedage:30 becomes undefinedundefined"age":30.undefinedundefined
    • undefinedundefined
    undefinedundefined

    undefinedundefinedJSON.stringify can be applied to primitives as well.undefinedundefined

    undefinedundefined

    JSON supports following data types:

    undefinedundefined
      undefinedundefined
    • Objects undefinedundefined{ ... }undefinedundefined
    • undefinedundefined
    • Arrays undefinedundefined[ ... ]undefinedundefined
    • undefinedundefined
    • Primitives: undefinedundefined
        undefinedundefined
      • strings,
      • undefinedundefined
      • numbers,
      • undefinedundefined
      • boolean values undefinedundefinedtrue/false,undefinedundefined
      • undefinedundefined
      • undefinedundefinednull.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run // a number in JSON is just a number alert( JSON.stringify(1) ) // 1

    undefinedundefined

    // a string in JSON is still a string, but double-quoted alert( JSON.stringify(‘test') ) // "test"

    undefinedundefined

    alert( JSON.stringify(true) ); // true

    undefinedundefined

    alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]

    undefinedundefined

    JSON is data-only language-independent specification, so some JavaScript-specific object properties are skipped by undefinedundefinedJSON.stringify.undefinedundefined

    undefinedundefined

    Namely:

    undefinedundefined
      undefinedundefined
    • Function properties (methods).
    • undefinedundefined
    • Symbolic keys and values.
    • undefinedundefined
    • Properties that store undefinedundefinedundefined.undefinedundefined
    • undefinedundefined
    undefinedundefined

    run let user = { sayHi() { // ignored alert("Hello"); }, [Symbol("id")]: 123, // ignored something: undefined // ignored };

    undefinedundefined

    alert( JSON.stringify(user) ); // {} (empty object)

    undefinedundefined

    Usually that's fine. If that's not what we want, then soon we'll see how to customize the process.

    undefinedundefined

    The great thing is that nested objects are supported and converted automatically.

    undefinedundefined

    For instance:

    undefinedundefined

    run let meetup = { title: "Conference", undefinedundefined! room: { number: 23, participants: ["john", "ann"] } undefinedundefined/! };undefinedundefined

    undefinedundefined

    alert( JSON.stringify(meetup) ); /* The whole structure is stringified: { "title":"Conference", "room":{"number":23,"participants":["john","ann"]}, } */

    undefinedundefined

    The important limitation: there must be no circular references.

    undefinedundefined

    For instance:

    undefinedundefined

    run let room = { number: 23 };

    undefinedundefined

    let meetup = { title: "Conference", participants: ["john", "ann"] };

    undefinedundefined

    meetup.place = room; // meetup references room room.occupiedBy = meetup; // room references meetup

    undefinedundefined

    undefinedundefined! JSON.stringify(meetup); // Error: Converting circular structure to JSON undefinedundefined/! undefinedundefined

    undefinedundefined

    Here, the conversion fails, because of circular reference: undefinedundefinedroom.occupiedBy references undefinedundefinedmeetup, and undefinedundefinedmeetup.place references undefinedundefinedroom:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Excluding and transforming: replacer

    undefinedundefined

    The full syntax of undefinedundefinedJSON.stringify is:undefinedundefined

    undefinedundefinedundefinedundefined
    undefinedundefined
    value
    undefinedundefined
    A value to encode.
    undefinedundefined
    replacer
    undefinedundefined
    Array of properties to encode or a mapping function undefinedundefinedfunction(key, value). undefinedundefined
    undefinedundefined
    space
    undefinedundefined
    Amount of space to use for formatting
    undefinedundefined
    undefinedundefined

    Most of the time, undefinedundefinedJSON.stringify is used with the first argument only. But if we need to fine-tune the replacement process, like to filter out circular references, we can use the second argument of undefinedundefinedJSON.stringify.undefinedundefined

    undefinedundefined

    If we pass an array of properties to it, only these properties will be encoded.

    undefinedundefined

    For instance:

    undefinedundefined

    run let room = { number: 23 };

    undefinedundefined

    let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup references room };

    undefinedundefined

    room.occupiedBy = meetup; // room references meetup

    undefinedundefined

    alert( JSON.stringify(meetup, undefinedundefined![‘title', ‘participants']undefinedundefined/!) ); // {"title":"Conference","participants":[{},{}]} undefinedundefined

    undefinedundefined

    Here we are probably too strict. The property list is applied to the whole object structure. So the objects in undefinedundefinedparticipants are empty, because undefinedundefinedname is not in the list.undefinedundefined

    undefinedundefined

    Let's include in the list every property except undefinedundefinedroom.occupiedBy that would cause the circular reference:undefinedundefined

    undefinedundefined

    run let room = { number: 23 };

    undefinedundefined

    let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup references room };

    undefinedundefined

    room.occupiedBy = meetup; // room references meetup

    undefinedundefined

    alert( JSON.stringify(meetup, undefinedundefined![‘title', ‘participants', ‘place', ‘name', ‘number']undefinedundefined/!) ); /undefinedundefined { "title":"Conference", "participants":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } / undefinedundefined

    undefinedundefined

    Now everything except undefinedundefinedoccupiedBy is serialized. But the list of properties is quite long.undefinedundefined

    undefinedundefined

    Fortunately, we can use a function instead of an array as the undefinedundefinedreplacer.undefinedundefined

    undefinedundefined

    The function will be called for every undefinedundefined(key, value) pair and should return the "replaced" value, which will be used instead of the original one. Or undefinedundefinedundefined if the value is to be skipped.undefinedundefined

    undefinedundefined

    In our case, we can return undefinedundefinedvalue "as is" for everything except undefinedundefinedoccupiedBy. To ignore undefinedundefinedoccupiedBy, the code below returns undefinedundefinedundefined:undefinedundefined

    undefinedundefined

    run let room = { number: 23 };

    undefinedundefined

    let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup references room };

    undefinedundefined

    room.occupiedBy = meetup; // room references meetup

    undefinedundefined

    alert( JSON.stringify(meetup, function replacer(key, value) { alert(undefinedundefined${key}: ${value}); return (key == ‘occupiedBy') ? undefined : value; }));undefinedundefined

    undefinedundefined
    undefinedundefined
    /* key:value pairs that come to replacer:
    undefinedundefined
    undefinedundefined
    undefinedundefined      [object Object]undefinedundefined
    title: Conference participants: [object Object],[object Object] 0: [object Object] name: John 1: [object Object] name: Alice place: [object Object] number: 23 occupiedBy: [object Object] */ undefinedundefined
    undefinedundefined
    undefinedundefined

    Please note that undefinedundefinedreplacer function gets every key/value pair including nested objects and array items. It is applied recursively. The value of undefinedundefinedthis inside undefinedundefinedreplacer is the object that contains the current property.undefinedundefined

    undefinedundefined

    The first call is special. It is made using a special "wrapper object": undefinedundefined{"": meetup}. In other words, the first undefinedundefined(key, value) pair has an empty key, and the value is the target object as a whole. That's why the first line is undefinedundefined":[object Object]" in the example above.undefinedundefined

    undefinedundefined

    The idea is to provide as much power for undefinedundefinedreplacer as possible: it has a chance to analyze and replace/skip even the whole object if necessary.undefinedundefined

    undefinedundefined

    Formatting: space

    undefinedundefined

    The third argument of undefinedundefinedJSON.stringify(value, replacer, space) is the number of spaces to use for pretty formatting.undefinedundefined

    undefinedundefined

    Previously, all stringified objects had no indents and extra spaces. That's fine if we want to send an object over a network. The undefinedundefinedspace argument is used exclusively for a nice output.undefinedundefined

    undefinedundefined

    Here undefinedundefinedspace = 2 tells JavaScript to show nested objects on multiple lines, with indentation of 2 spaces inside an object:undefinedundefined

    undefinedundefined

    run let user = { name: "John", age: 25, roles: { isAdmin: false, isEditor: true } };

    undefinedundefined

    alert(JSON.stringify(user, null, 2)); /* two-space indents: { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */

    undefinedundefined

    /* for JSON.stringify(user, null, 4) the result would be more indented: { "name": "John", "age": 25, "roles": { "isAdmin": false, "isEditor": true } } */

    undefinedundefined

    The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces.

    undefinedundefined

    The undefinedundefinedspace parameter is used solely for logging and nice-output purposes.undefinedundefined

    undefinedundefined

    Custom "toJSON"

    undefinedundefined

    Like undefinedundefinedtoString for string conversion, an object may provide method undefinedundefinedtoJSON for to-JSON conversion. undefinedundefinedJSON.stringify automatically calls it if available.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let room = { number: 23 };

    undefinedundefined

    let meetup = { title: "Conference", date: new Date(Date.UTC(2017, 0, 1)), room };

    undefinedundefined

    alert( JSON.stringify(meetup) ); /undefinedundefined { "title":"Conference", !undefinedundefined "date":"2017-01-01T00:00:00.000Z", // (1) /!undefinedundefined "room": {"number":23} // (2) } / undefinedundefined

    undefinedundefined

    Here we can see that undefinedundefineddateundefinedundefined(1) became a string. That's because all dates have a built-in undefinedundefinedtoJSON method which returns such kind of string.undefinedundefined

    undefinedundefined

    Now let's add a custom undefinedundefinedtoJSON for our object undefinedundefinedroomundefinedundefined(2):undefinedundefined

    undefinedundefined

    run let room = { number: 23, undefinedundefined! toJSON() { return this.number; } undefinedundefined/! };undefinedundefined

    undefinedundefined

    let meetup = { title: "Conference", room };

    undefinedundefined

    undefinedundefined! alert( JSON.stringify(room) ); // 23 undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( JSON.stringify(meetup) ); /undefinedundefined { "title":"Conference", !undefinedundefined "room": 23 /!undefinedundefined } / undefinedundefined

    undefinedundefined

    As we can see, undefinedundefinedtoJSON is used both for the direct call undefinedundefinedJSON.stringify(room) and when undefinedundefinedroom is nested in another encoded object.undefinedundefined

    undefinedundefined

    JSON.parse

    undefinedundefined

    To decode a JSON-string, we need another method named undefinedundefinedJSON.parse.undefinedundefined

    undefinedundefined

    The syntax:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet value undefinedundefined=undefinedundefinedJSON.undefinedundefinedparse(strundefinedundefined, [reviver])undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefined
    str
    undefinedundefined
    JSON-string to parse.
    undefinedundefined
    reviver
    undefinedundefined
    Optional function(key,value) that will be called for each undefinedundefined(key, value) pair and can transform the value. undefinedundefined
    undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run // stringified array let numbers = "[0, 1, 2, 3]";

    undefinedundefined

    numbers = JSON.parse(numbers);

    undefinedundefined

    alert( numbers[1] ); // 1

    undefinedundefined

    Or for nested objects:

    undefinedundefined

    run let userData = ‘{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }''';

    undefinedundefined

    let user = JSON.parse(userData);

    undefinedundefined

    alert( user.friends[1] ); // 1

    undefinedundefined

    The JSON may be as complex as necessary, objects and arrays can include other objects and arrays. But they must obey the same JSON format.

    undefinedundefined

    Here are typical mistakes in hand-written JSON (sometimes we have to write it for debugging purposes):

    undefinedundefined undefinedundefined

    Besides, JSON does not support comments. Adding a comment to JSON makes it invalid.

    undefinedundefined

    There's another format named undefinedundefinedJSON5, which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language.undefinedundefined

    undefinedundefined

    The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm.

    undefinedundefined

    Using reviver

    undefinedundefined

    Imagine, we got a stringified undefinedundefinedmeetup object from the server.undefinedundefined

    undefinedundefined

    It looks like this:

    undefinedundefinedundefinedundefined

    …And now we need to undefinedundefineddeserialize it, to turn back into JavaScript object.undefinedundefined

    undefinedundefined

    Let's do it by calling undefinedundefinedJSON.parse:undefinedundefined

    undefinedundefined

    run let str = ‘{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}''';

    undefinedundefined

    let meetup = JSON.parse(str);

    undefinedundefined

    undefinedundefined! alert( meetup.date.getDate() ); // Error! undefinedundefined/! undefinedundefined

    undefinedundefined

    Whoops! An error!

    undefinedundefined

    The value of undefinedundefinedmeetup.date is a string, not a undefinedundefinedDate object. How could undefinedundefinedJSON.parse know that it should transform that string into a undefinedundefinedDate?undefinedundefined

    undefinedundefined

    Let's pass to undefinedundefinedJSON.parse the reviving function as the second argument, that returns all values "as is", but undefinedundefineddate will become a undefinedundefinedDate:undefinedundefined

    undefinedundefined

    run let str = ‘{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}''';

    undefinedundefined

    undefinedundefined! let meetup = JSON.parse(str, function(key, value) { if (key == ‘date') return new Date(value); return value; }); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( meetup.date.getDate() ); // now works!

    undefinedundefined

    By the way, that works for nested objects as well:

    undefinedundefined

    ``undefinedundefinedjs run let schedule ={ "meetups": [ {"title":"Conference","date":"2017-11-30T12:00:00.000Z"}, {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"} ] }`;undefinedundefined

    undefinedundefined

    schedule = JSON.parse(schedule, function(key, value) { if (key == ‘date') return new Date(value); return value; });

    undefinedundefined

    undefinedundefined! alert( schedule.meetups[1].date.getDate() ); // works! undefinedundefined/! undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • JSON is a data format that has its own independent standard and libraries for most programming languages.
    • undefinedundefined
    • JSON supports plain objects, arrays, strings, numbers, booleans, and undefinedundefinednull.undefinedundefined
    • undefinedundefined
    • JavaScript provides methods undefinedundefinedJSON.stringify to serialize into JSON and undefinedundefinedJSON.parse to read from JSON.undefinedundefined
    • undefinedundefined
    • Both methods support transformer functions for smart reading/writing.
    • undefinedundefined
    • If an object has undefinedundefinedtoJSON, then it is called by undefinedundefinedJSON.stringify.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Recursion and stack

    undefinedundefined

    Let's return to functions and study them more in-depth.

    undefinedundefined

    Our first topic will be undefinedundefinedrecursion.undefinedundefined

    undefinedundefined

    If you are not new to programming, then it is probably familiar and you could skip this chapter.

    undefinedundefined

    Recursion is a programming pattern that is useful in situations when a task can be naturally split into several tasks of the same kind, but simpler. Or when a task can be simplified into an easy action plus a simpler variant of the same task. Or, as we'll see soon, to deal with certain data structures.

    undefinedundefined

    When a function solves a task, in the process it can call many other functions. A partial case of this is when a function calls undefinedundefineditself. That's called undefinedundefinedrecursion.undefinedundefined

    undefinedundefined

    Two ways of thinking

    undefinedundefined

    For something simple to start with - let's write a function undefinedundefinedpow(x, n) that raises undefinedundefinedx to a natural power of undefinedundefinedn. In other words, multiplies undefinedundefinedx by itself undefinedundefinedn times.undefinedundefined

    undefinedundefinedundefinedundefined

    There are two ways to implement it.

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      Iterative thinking: the undefinedundefinedfor loop:undefinedundefined

      undefinedundefined

      run function pow(x, n) { let result = 1;

      undefinedundefined

      // multiply result by x n times in the loop for (let i = 0; i < n; i++) { result *= x; }

      undefinedundefined

      return result; }

      undefinedundefined

      alert( pow(2, 3) ); // 8

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Recursive thinking: simplify the task and call self:

      undefinedundefined

      run function pow(x, n) { if (n == 1) { return x; } else { return x * pow(x, n - 1); } }

      undefinedundefined

      alert( pow(2, 3) ); // 8

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Please note how the recursive variant is fundamentally different.

    undefinedundefined

    When undefinedundefinedpow(x, n) is called, the execution splits into two branches:undefinedundefined

    undefinedundefinedundefinedundefined
      undefinedundefined
    1. If undefinedundefinedn == 1, then everything is trivial. It is called undefinedundefinedthe base of recursion, because it immediately produces the obvious result: undefinedundefinedpow(x, 1) equals undefinedundefinedx.undefinedundefined
    2. undefinedundefined
    3. Otherwise, we can represent undefinedundefinedpow(x, n) as undefinedundefinedx * pow(x, n - 1). In maths, one would write undefinedundefinedxundefinedundefinedn = x * xundefinedundefinedn-1undefinedundefined. This is called undefinedundefineda recursive step: we transform the task into a simpler action (multiplication by undefinedundefinedx) and a simpler call of the same task (undefinedundefinedpow with lower undefinedundefinedn). Next steps simplify it further and further until undefinedundefinedn reaches undefinedundefined1.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    We can also say that undefinedundefinedpowundefinedundefinedrecursively calls itself till undefinedundefinedn == 1.undefinedundefined

    undefinedundefined
    undefinedundefinedrecursive diagram of powundefinedundefined
    recursive diagram of pow
    undefinedundefined
    undefinedundefined

    For example, to calculate undefinedundefinedpow(2, 4) the recursive variant does these steps:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedpow(2, 4) = 2 * pow(2, 3)undefinedundefined
    2. undefinedundefined
    3. undefinedundefinedpow(2, 3) = 2 * pow(2, 2)undefinedundefined
    4. undefinedundefined
    5. undefinedundefinedpow(2, 2) = 2 * pow(2, 1)undefinedundefined
    6. undefinedundefined
    7. undefinedundefinedpow(2, 1) = 2undefinedundefined
    8. undefinedundefined
    undefinedundefined

    So, the recursion reduces a function call to a simpler one, and then - to even more simpler, and so on, until the result becomes obvious.

    undefinedundefined

    smart header="Recursion is usually shorter" A recursive solution is usually shorter than an iterative one.

    undefinedundefined

    Here we can rewrite the same using the conditional operator undefinedundefined? instead of undefinedundefinedif to make undefinedundefinedpow(x, n) more terse and still very readable:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run function pow(x, n) { return (n == 1) ? x : (x * pow(x, n - 1)); } undefinedundefined

    undefinedundefined

    The maximal number of nested calls (including the first one) is called undefinedundefinedrecursion depth. In our case, it will be exactly undefinedundefinedn.undefinedundefined

    undefinedundefined

    The maximal recursion depth is limited by JavaScript engine. We can rely on it being 10000, some engines allow more, but 100000 is probably out of limit for the majority of them. There are automatic optimizations that help alleviate this ("tail calls optimizations"), but they are not yet supported everywhere and work only in simple cases.

    undefinedundefined

    That limits the application of recursion, but it still remains very wide. There are many tasks where recursive way of thinking gives simpler code, easier to maintain.

    undefinedundefined

    The execution context and stack

    undefinedundefined

    Now let's examine how recursive calls work. For that we'll look under the hood of functions.

    undefinedundefined

    The information about the process of execution of a running function is stored in its undefinedundefinedexecution context.undefinedundefined

    undefinedundefined

    The undefinedundefinedexecution context is an internal data structure that contains details about the execution of a function: where the control flow is now, the current variables, the value of undefinedundefinedthis (we don't use it here) and few other internal details.undefinedundefined

    undefinedundefined

    One function call has exactly one execution context associated with it.

    undefinedundefined

    When a function makes a nested call, the following happens:

    undefinedundefined
      undefinedundefined
    • The current function is paused.
    • undefinedundefined
    • The execution context associated with it is remembered in a special data structure called undefinedundefinedexecution context stack.undefinedundefined
    • undefinedundefined
    • The nested call executes.
    • undefinedundefined
    • After it ends, the old execution context is retrieved from the stack, and the outer function is resumed from where it stopped.
    • undefinedundefined
    undefinedundefined

    Let's see what happens during the undefinedundefinedpow(2, 3) call.undefinedundefined

    undefinedundefined

    pow(2, 3)

    undefinedundefined

    In the beginning of the call undefinedundefinedpow(2, 3) the execution context will store variables: undefinedundefinedx = 2, n = 3, the execution flow is at line undefinedundefined1 of the function.undefinedundefined

    undefinedundefined

    We can sketch it as:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 3, at line 1 }undefinedundefinedpow(2, 3)undefinedundefined
    • undefinedundefined
    undefinedundefined

    That's when the function starts to execute. The condition undefinedundefinedn == 1 is falsy, so the flow continues into the second branch of undefinedundefinedif:undefinedundefined

    undefinedundefined

    run function pow(x, n) { if (n == 1) { return x; } else { undefinedundefined! return x * pow(x, n - 1); undefinedundefined/! } }undefinedundefined

    undefinedundefined

    alert( pow(2, 3) );

    undefinedundefined

    The variables are same, but the line changes, so the context is now:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 3, at line 5 }undefinedundefinedpow(2, 3)undefinedundefined
    • undefinedundefined
    undefinedundefined

    To calculate undefinedundefinedx * pow(x, n - 1), we need to make a subcall of undefinedundefinedpow with new arguments undefinedundefinedpow(2, 2).undefinedundefined

    undefinedundefined

    pow(2, 2)

    undefinedundefined

    To do a nested call, JavaScript remembers the current execution context in the undefinedundefinedexecution context stack.undefinedundefined

    undefinedundefined

    Here we call the same function undefinedundefinedpow, but it absolutely doesn't matter. The process is the same for all functions:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. The current context is "remembered" on top of the stack.
    2. undefinedundefined
    3. The new context is created for the subcall.
    4. undefinedundefined
    5. When the subcall is finished - the previous context is popped from the stack, and its execution continues.
    6. undefinedundefined
    undefinedundefined

    Here's the context stack when we entered the subcall undefinedundefinedpow(2, 2):undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 2, at line 1 }undefinedundefinedpow(2, 2)undefinedundefined
    • undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 3, at line 5 }undefinedundefinedpow(2, 3)undefinedundefined
    • undefinedundefined
    undefinedundefined

    The new current execution context is on top (and bold), and previous remembered contexts are below.

    undefinedundefined

    When we finish the subcall - it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped.

    undefinedundefined
    undefinedundefinedHere in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`.
    So it would be more precise to say that the execution resumes "immediately after the subcall".undefinedundefined
    undefinedundefined

    pow(2, 1)

    undefinedundefined

    The process repeats: a new subcall is made at line undefinedundefined5, now with arguments undefinedundefinedx=2, undefinedundefinedn=1.undefinedundefined

    undefinedundefined

    A new execution context is created, the previous one is pushed on top of the stack:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 1, at line 1 }undefinedundefinedpow(2, 1)undefinedundefined
    • undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 2, at line 5 }undefinedundefinedpow(2, 2)undefinedundefined
    • undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 3, at line 5 }undefinedundefinedpow(2, 3)undefinedundefined
    • undefinedundefined
    undefinedundefined

    There are 2 old contexts now and 1 currently running for undefinedundefinedpow(2, 1).undefinedundefined

    undefinedundefined

    The exit

    undefinedundefined

    During the execution of undefinedundefinedpow(2, 1), unlike before, the condition undefinedundefinedn == 1 is truthy, so the first branch of undefinedundefinedif works:undefinedundefined

    undefinedundefinedundefinedundefined

    There are no more nested calls, so the function finishes, returning undefinedundefined2.undefinedundefined

    undefinedundefined

    As the function finishes, its execution context is not needed anymore, so it's removed from the memory. The previous one is restored off the top of the stack:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 2, at line 5 }undefinedundefinedpow(2, 2)undefinedundefined
    • undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 3, at line 5 }undefinedundefinedpow(2, 3)undefinedundefined
    • undefinedundefined
    undefinedundefined

    The execution of undefinedundefinedpow(2, 2) is resumed. It has the result of the subcall undefinedundefinedpow(2, 1), so it also can finish the evaluation of undefinedundefinedx * pow(x, n - 1), returning undefinedundefined4.undefinedundefined

    undefinedundefined

    Then the previous context is restored:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedContext: { x: 2, n: 3, at line 5 }undefinedundefinedpow(2, 3)undefinedundefined
    • undefinedundefined
    undefinedundefined

    When it finishes, we have a result of undefinedundefinedpow(2, 3) = 8.undefinedundefined

    undefinedundefined

    The recursion depth in this case was: undefinedundefined3.undefinedundefined

    undefinedundefined

    As we can see from the illustrations above, recursion depth equals the maximal number of context in the stack.

    undefinedundefined

    Note the memory requirements. Contexts take memory. In our case, raising to the power of undefinedundefinedn actually requires the memory for undefinedundefinedn contexts, for all lower values of undefinedundefinedn.undefinedundefined

    undefinedundefined

    A loop-based algorithm is more memory-saving:

    undefinedundefinedundefinedundefined

    The iterative undefinedundefinedpow uses a single context changing undefinedundefinedi and undefinedundefinedresult in the process. Its memory requirements are small, fixed and do not depend on undefinedundefinedn.undefinedundefined

    undefinedundefined

    undefinedundefinedAny recursion can be rewritten as a loop. The loop variant usually can be made more effective.undefinedundefined

    undefinedundefined

    …But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts.

    undefinedundefined

    Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used.

    undefinedundefined

    Recursive traversals

    undefinedundefined

    Another great application of the recursion is a recursive traversal.

    undefinedundefined

    Imagine, we have a company. The staff structure can be presented as an object:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet company undefinedundefined=undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedsalesundefinedundefined: [undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinednameundefinedundefined:undefinedundefined'John'undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedsalaryundefinedundefined:undefinedundefined1000undefinedundefinedundefinedundefinedundefinedundefined},undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinednameundefinedundefined:undefinedundefined'Alice'undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedsalaryundefinedundefined:undefinedundefined1600undefinedundefinedundefinedundefinedundefinedundefined}]undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefineddevelopmentundefinedundefined:undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedsitesundefinedundefined: [undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinednameundefinedundefined:undefinedundefined'Peter'undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedsalaryundefinedundefined:undefinedundefined2000undefinedundefinedundefinedundefinedundefinedundefined},undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinednameundefinedundefined:undefinedundefined'Alex'undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedsalaryundefinedundefined:undefinedundefined1800undefinedundefinedundefinedundefinedundefinedundefined}]undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedinternalsundefinedundefined: [undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinednameundefinedundefined:undefinedundefined'Jack'undefinedundefined,undefinedundefinedundefinedundefinedundefinedundefinedsalaryundefinedundefined:undefinedundefined1300undefinedundefinedundefinedundefinedundefinedundefined}]undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    In other words, a company has departments.

    undefinedundefined
      undefinedundefined
    • A department may have an array of staff. For instance, undefinedundefinedsales department has 2 employees: John and Alice.undefinedundefined
    • undefinedundefined
    • Or a department may split into subdepartments, like undefinedundefineddevelopment has two branches: undefinedundefinedsites and undefinedundefinedinternals. Each of them has their own staff.undefinedundefined
    • undefinedundefined
    • undefinedundefined

      It is also possible that when a subdepartment grows, it divides into subsubdepartments (or teams).

      undefinedundefined

      For instance, the undefinedundefinedsites department in the future may be split into teams for undefinedundefinedsiteA and undefinedundefinedsiteB. And they, potentially, can split even more. That's not on the picture, just something to have in mind.undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Now let's say we want a function to get the sum of all salaries. How can we do that?

    undefinedundefined

    An iterative approach is not easy, because the structure is not simple. The first idea may be to make a undefinedundefinedfor loop over undefinedundefinedcompany with nested subloop over 1st level departments. But then we need more nested subloops to iterate over the staff in 2nd level departments like undefinedundefinedsites… And then another subloop inside those for 3rd level departments that might appear in the future? If we put 3-4 nested subloops in the code to traverse a single object, it becomes rather ugly.undefinedundefined

    undefinedundefined

    Let's try recursion.

    undefinedundefined

    As we can see, when our function gets a department to sum, there are two possible cases:

    undefinedundefined
      undefinedundefined
    1. Either it's a "simple" department with an undefinedundefinedarray of people - then we can sum the salaries in a simple loop.undefinedundefined
    2. undefinedundefined
    3. Or it's undefinedundefinedan object with undefinedundefinedN subdepartments - then we can make undefinedundefinedN recursive calls to get the sum for each of the subdeps and combine the results.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    The 1st case is the base of recursion, the trivial case, when we get an array.

    undefinedundefined

    The 2nd case when we get an object is the recursive step. A complex task is split into subtasks for smaller departments. They may in turn split again, but sooner or later the split will finish at (1).

    undefinedundefined

    The algorithm is probably even easier to read from the code:

    undefinedundefined

    run let company = { // the same object, compressed for brevity sales: [{name: ‘John', salary: 1000}, {name: ‘Alice', salary: 1600 }], development: { sites: [{name: ‘Peter', salary: 2000}, {name: ‘Alex', salary: 1800 }], internals: [{name: ‘Jack', salary: 1300}] } };

    undefinedundefined

    // The function to do the job undefinedundefined! function sumSalaries(department) { if (Array.isArray(department)) { // case (1) return department.reduce((prev, current) => prev + current.salary, 0); // sum the array } else { // case (2) let sum = 0; for (let subdep of Object.values(department)) { sum += sumSalaries(subdep); // recursively call for subdepartments, sum the results } return sum; } } undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(sumSalaries(company)); // 7700

    undefinedundefined

    The code is short and easy to understand (hopefully?). That's the power of recursion. It also works for any level of subdepartment nesting.

    undefinedundefined

    Here's the diagram of calls:

    undefinedundefined
    undefinedundefinedrecursive salariesundefinedundefined
    recursive salaries
    undefinedundefined
    undefinedundefined

    We can easily see the principle: for an object undefinedundefined{...} subcalls are made, while arrays undefinedundefined[...] are the "leaves" of the recursion tree, they give immediate result.undefinedundefined

    undefinedundefined

    Note that the code uses smart features that we've covered before:

    undefinedundefined
      undefinedundefined
    • Method undefinedundefinedarr.reduce explained in the chapter undefinedundefinedinfo:array-methods to get the sum of the array.undefinedundefined
    • undefinedundefined
    • Loop undefinedundefinedfor(val of Object.values(obj)) to iterate over object values: undefinedundefinedObject.values returns an array of them.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Recursive structures

    undefinedundefined

    A recursive (recursively-defined) data structure is a structure that replicates itself in parts.

    undefinedundefined

    We've just seen it in the example of a company structure above.

    undefinedundefined

    A company undefinedundefineddepartment is: - Either an array of people. - Or an object with undefinedundefineddepartments.undefinedundefined

    undefinedundefined

    For web-developers there are much better-known examples: HTML and XML documents.

    undefinedundefined

    In the HTML document, an undefinedundefinedHTML-tag may contain a list of: - Text pieces. - HTML-comments. - Other undefinedundefinedHTML-tags (that in turn may contain text pieces/comments or other tags etc).undefinedundefined

    undefinedundefined

    That's once again a recursive definition.

    undefinedundefined

    For better understanding, we'll cover one more recursive structure named "Linked list" that might be a better alternative for arrays in some cases.

    undefinedundefined

    Linked list

    undefinedundefined

    Imagine, we want to store an ordered list of objects.

    undefinedundefined

    The natural choice would be an array:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet arr undefinedundefined= [obj1undefinedundefined, obj2undefinedundefined, obj3]undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    …But there's a problem with arrays. The "delete element" and "insert element" operations are expensive. For instance, undefinedundefinedarr.unshift(obj) operation has to renumber all elements to make room for a new undefinedundefinedobj, and if the array is big, it takes time. Same with undefinedundefinedarr.shift().undefinedundefined

    undefinedundefined

    The only structural modifications that do not require mass-renumbering are those that operate with the end of array: undefinedundefinedarr.push/pop. So an array can be quite slow for big queues, when we have to work with the beginning.undefinedundefined

    undefinedundefined

    Alternatively, if we really need fast insertion/deletion, we can choose another data structure called a undefinedundefinedlinked list.undefinedundefined

    undefinedundefined

    The undefinedundefinedlinked list element is recursively defined as an object with: - undefinedundefinedvalue. - undefinedundefinednext property referencing the next undefinedundefinedlinked list element or undefinedundefinednull if that's the end.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    Graphical representation of the list:

    undefinedundefined
    undefinedundefinedlinked listundefinedundefined
    linked list
    undefinedundefined
    undefinedundefined

    An alternative code for creation:

    undefinedundefined

    undefinedundefinedjs no-beautify let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; list.next.next.next.next = null;undefinedundefined

    undefinedundefined

    Here we can even more clearly see that there are multiple objects, each one has the undefinedundefinedvalue and undefinedundefinednext pointing to the neighbour. The undefinedundefinedlist variable is the first object in the chain, so following undefinedundefinednext pointers from it we can reach any element.undefinedundefined

    undefinedundefined

    The list can be easily split into multiple parts and later joined back:

    undefinedundefinedundefinedundefined
    undefinedundefinedlinked list splitundefinedundefined
    linked list split
    undefinedundefined
    undefinedundefined

    To join:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlist.undefinedundefinednext.undefinedundefinednextundefinedundefined= secondListundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    And surely we can insert or remove items in any place.

    undefinedundefined

    For instance, to prepend a new value, we need to update the head of the list:

    undefinedundefinedundefinedundefined
    undefinedundefinedlinked listundefinedundefined
    linked list
    undefinedundefined
    undefinedundefined

    To remove a value from the middle, change undefinedundefinednext of the previous one:undefinedundefined

    undefinedundefinedundefinedundefined
    undefinedundefinedlinked listundefinedundefined
    linked list
    undefinedundefined
    undefinedundefined

    We made undefinedundefinedlist.next jump over undefinedundefined1 to value undefinedundefined2. The value undefinedundefined1 is now excluded from the chain. If it's not stored anywhere else, it will be automatically removed from the memory.undefinedundefined

    undefinedundefined

    Unlike arrays, there's no mass-renumbering, we can easily rearrange elements.

    undefinedundefined

    Naturally, lists are not always better than arrays. Otherwise everyone would use only lists.

    undefinedundefined

    The main drawback is that we can't easily access an element by its number. In an array that's easy: undefinedundefinedarr[n] is a direct reference. But in the list we need to start from the first item and go undefinedundefinednextundefinedundefinedN times to get the Nth element.undefinedundefined

    undefinedundefined

    …But we don't always need such operations. For instance, when we need a queue or even a undefinedundefineddeque - the ordered structure that must allow very fast adding/removing elements from both ends, but access to its middle is not needed.undefinedundefined

    undefinedundefined

    Lists can be enhanced: - We can add property undefinedundefinedprev in addition to undefinedundefinednext to reference the previous element, to move back easily. - We can also add a variable named undefinedundefinedtail referencing the last element of the list (and update it when adding/removing elements from the end). - …The data structure may vary according to our needs.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Terms: - undefinedundefinedRecursion is a programming term that means calling a function from itself. Recursive functions can be used to solve tasks in elegant ways.undefinedundefined

    undefinedundefined
    undefinedundefinedWhen a function calls itself, that's called a *recursion step*. The *basis* of recursion is function arguments that make the task so simple that the function does not make further calls.undefinedundefined
    undefinedundefined
      undefinedundefined
    • undefinedundefined

      A undefinedundefinedrecursively-defined data structure is a data structure that can be defined using itself.undefinedundefined

      undefinedundefined

      For instance, the linked list can be defined as a data structure consisting of an object referencing a list (or null).

      undefinedundefined
      undefinedundefined
      undefinedundefinedundefinedundefinedlist undefinedundefined=undefinedundefined{ valueundefinedundefined, next undefinedundefined-> list undefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
      undefinedundefined
      undefinedundefined

      Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches.

      undefinedundefined

      Recursive functions can be used to walk them as we've seen in the undefinedundefinedsumSalary example.undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Any recursive function can be rewritten into an iterative one. And that's sometimes required to optimize stuff. But for many tasks a recursive solution is fast enough and easier to write and support.

    undefinedundefined

    Hello, world!

    undefinedundefined

    This part of the tutorial is about core JavaScript, the language itself.

    undefinedundefined

    But we need a working environment to run our scripts and, since this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like undefinedundefinedalert) to a minimum so that you don't spend time on them if you plan to concentrate on another environment (like Node.js). We'll focus on JavaScript in the browser in the undefinedundefinednext part of the tutorial.undefinedundefined

    undefinedundefined

    So first, let's see how we attach a script to a webpage. For server-side environments (like Node.js), you can execute the script with a command like undefinedundefined"node my.js".undefinedundefined

    undefinedundefined

    The "script" tag

    undefinedundefined

    JavaScript programs can be inserted almost anywhere into an HTML document using the undefinedundefined<script> tag.undefinedundefined

    undefinedundefined

    For instance:

    run height=100 <!DOCTYPE HTML> undefinedundefinedundefinedundefined undefinedundefined

    Before the script…

    undefinedundefined!undefinedundefined undefinedundefined

    undefinedundefined/!undefinedundefined

    undefinedundefined

    …After the script.

    undefinedundefined undefinedundefined undefinedundefined

    undefinedundefined
    undefinedundefinedYou can run the example by clicking the "Play" button in the right-top corner of the box above.undefinedundefined
    undefinedundefined

    The undefinedundefined<script> tag contains JavaScript code which is automatically executed when the browser processes the tag.undefinedundefined

    undefinedundefined

    Modern markup

    undefinedundefined

    The undefinedundefined<script> tag has a few attributes that are rarely used nowadays but can still be found in old code:undefinedundefined

    undefinedundefined
    undefinedundefined
    The undefinedundefinedtype attribute: undefinedundefined<script undefinedundefinedtype=…>undefinedundefinedundefinedundefined
    undefinedundefined
    The old HTML standard, HTML4, required a script to have a undefinedundefinedtype. Usually it was undefinedundefinedtype="text/javascript". It's not required anymore. Also, the modern HTML standard totally changed the meaning of this attribute. Now, it can be used for JavaScript modules. But that's an advanced topic, we'll talk about modules in another part of the tutorial. undefinedundefined
    undefinedundefined
    The undefinedundefinedlanguage attribute: undefinedundefined<script undefinedundefinedlanguage=…>undefinedundefinedundefinedundefined
    undefinedundefined
    This attribute was meant to show the language of the script. This attribute no longer makes sense because JavaScript is the default language. There is no need to use it.
    undefinedundefined
    Comments before and after scripts.
    undefinedundefined
    undefinedundefined

    In really ancient books and guides, you may find comments inside undefinedundefined<script> tags, like this:undefinedundefined

    undefinedundefined

    undefinedundefinedhtml no-beautify <script type="text/javascript"><!-- ... //--></script>undefinedundefined

    undefinedundefined

    This trick isn't used in modern JavaScript. These comments hide JavaScript code from old browsers that didn't know how to process the undefinedundefined<script> tag. Since browsers released in the last 15 years don't have this issue, this kind of comment can help you identify really old code.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    External scripts

    undefinedundefined

    If we have a lot of JavaScript code, we can put it into a separate file.

    undefinedundefined

    Script files are attached to HTML with the undefinedundefinedsrc attribute:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined<scriptundefinedundefined src=undefinedundefined"/path/to/script.js"undefinedundefined></script>undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Here, undefinedundefined/path/to/script.js is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, undefinedundefinedsrc="script.js" would mean a file undefinedundefined"script.js" in the current folder.undefinedundefined

    undefinedundefined

    We can give a full URL as well. For instance:

    undefinedundefinedundefinedundefined

    To attach several scripts, use multiple tags:

    undefinedundefinedundefinedundefined
    undefinedundefinedAs a rule, only the simplest scripts are put into HTML. More complex ones reside in separate files.
    The benefit of a separate file is that the browser will download it and store it in its [cache](https://en.wikipedia.org/wiki/Web_cache).
    Other pages that reference the same script will take it from the cache instead of downloading it, so the file is actually downloaded only once.
    That reduces traffic and makes pages faster.undefinedundefined
    undefinedundefinedwarn header="Ifsrcundefinedundefinedis set, the script content is ignored." A singleundefinedundefined undefinedundefined

    undefinedundefined

    We must choose either an external undefinedundefined<script src="…"> or a regular undefinedundefined<script> with code.undefinedundefined

    undefinedundefined

    The example above can be split into two scripts to work:

    undefinedundefinedundefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • We can use a undefinedundefined<script> tag to add JavaScript code to a page.undefinedundefined
    • undefinedundefined
    • The undefinedundefinedtype and undefinedundefinedlanguage attributes are not required.undefinedundefined
    • undefinedundefined
    • A script in an external file can be inserted with undefinedundefined<script src="path/to/script.js"></script>.undefinedundefined
    • undefinedundefined
    undefinedundefined

    There is much more to learn about browser scripts and their interaction with the webpage. But let's keep in mind that this part of the tutorial is devoted to the JavaScript language, so we shouldn't distract ourselves with browser-specific implementations of it. We'll be using the browser as a way to run JavaScript, which is very convenient for online reading, but only one of many.

    undefinedundefined

    Rest parameters and spread syntax

    undefinedundefined

    Many JavaScript built-in functions support an arbitrary number of arguments.

    undefinedundefined

    For instance:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedMath.max(arg1, arg2, ..., argN) - returns the greatest of the arguments.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.assign(dest, src1, ..., srcN) - copies properties from undefinedundefinedsrc1..N into undefinedundefineddest.undefinedundefined
    • undefinedundefined
    • …and so on.
    • undefinedundefined
    undefinedundefined

    In this chapter we'll learn how to do the same. And also, how to pass arrays to such functions as parameters.

    undefinedundefined

    Rest parameters undefinedundefined...undefinedundefined

    undefinedundefined

    A function can be called with any number of arguments, no matter how it is defined.

    undefinedundefined

    Like here: run function sum(a, b) { return a + b; }

    undefinedundefined

    alert( sum(1, 2, 3, 4, 5) );

    undefinedundefined

    There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted.

    undefinedundefined

    The rest of the parameters can be included in the function definition by using three dots undefinedundefined... followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array".undefinedundefined

    undefinedundefined

    For instance, to gather all arguments into array undefinedundefinedargs:undefinedundefined

    undefinedundefined

    run function sumAll(…args) { // args is the name for the array let sum = 0;

    undefinedundefined

    for (let arg of args) sum += arg;

    undefinedundefined

    return sum; }

    undefinedundefined

    alert( sumAll(1) ); // 1 alert( sumAll(1, 2) ); // 3 alert( sumAll(1, 2, 3) ); // 6

    undefinedundefined

    We can choose to get the first parameters as variables, and gather only the rest.

    undefinedundefined

    Here the first two arguments go into variables and the rest go into undefinedundefinedtitles array:undefinedundefined

    undefinedundefined

    run function showName(firstName, lastName, …titles) { alert( firstName + ''' ''' + lastName ); // Julius Caesar

    undefinedundefined

    // the rest go into titles array // i.e. titles = ["Consul", "Imperator"] alert( titles[0] ); // Consul alert( titles[1] ); // Imperator alert( titles.length ); // 2 }

    undefinedundefined

    showName("Julius", "Caesar", "Consul", "Imperator");

    undefinedundefined

    warn header="The rest parameters must be at the end" The rest parameters gather all remaining arguments, so the following does not make sense and causes an error:

    undefinedundefinedundefinedundefined

    The undefinedundefined...rest must always be last. undefinedundefined

    undefinedundefined

    The "arguments" variable

    undefinedundefined

    There is also a special array-like object named undefinedundefinedarguments that contains all arguments by their index.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run function showName() { alert( arguments.length ); alert( arguments[0] ); alert( arguments[1] );

    undefinedundefined

    // it's iterable // for(let arg of arguments) alert(arg); }

    undefinedundefined

    // shows: 2, Julius, Caesar showName("Julius", "Caesar");

    undefinedundefined

    // shows: 1, Ilya, undefined (no second argument) showName("Ilya");

    undefinedundefined

    In old times, rest parameters did not exist in the language, and using undefinedundefinedarguments was the only way to get all arguments of the function. And it still works, we can find it in the old code.undefinedundefined

    undefinedundefined

    But the downside is that although undefinedundefinedarguments is both array-like and iterable, it's not an array. It does not support array methods, so we can't call undefinedundefinedarguments.map(...) for example.undefinedundefined

    undefinedundefined

    Also, it always contains all arguments. We can't capture them partially, like we did with rest parameters.

    undefinedundefined

    So when we need these features, then rest parameters are preferred.

    undefinedundefined

    undefinedundefinedsmart header="Arrow functions do not have"arguments"undefinedundefined" If we access thearguments` object from an arrow function, it takes them from the outer "normal" function.undefinedundefined

    undefinedundefined

    Here's an example:

    undefinedundefined

    run function f() { let showArg = () => alert(arguments[0]); showArg(); }

    undefinedundefined

    f(1); // 1

    undefinedundefined
    undefinedundefined
    As we remember, arrow functions don't have their own `this`. Now we know they don't have the special `arguments` object either.undefinedundefined
    undefinedundefined

    Spread syntax [#spread-syntax]

    undefinedundefined

    We've just seen how to get an array from the list of parameters.

    undefinedundefined

    But sometimes we need to do exactly the reverse.

    undefinedundefined

    For instance, there's a built-in function undefinedundefinedMath.max that returns the greatest number from a list:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( Math.max(3, 5, 1) ); // 5undefinedundefined

    undefinedundefined

    Now let's say we have an array undefinedundefined[3, 5, 1]. How do we call undefinedundefinedMath.max with it?undefinedundefined

    undefinedundefined

    Passing it "as is" won't work, because undefinedundefinedMath.max expects a list of numeric arguments, not a single array:undefinedundefined

    undefinedundefined

    run let arr = [3, 5, 1];

    undefinedundefined

    undefinedundefined! alert( Math.max(arr) ); // NaN undefinedundefined/! undefinedundefined

    undefinedundefined

    And surely we can't manually list items in the code undefinedundefinedMath.max(arr[0], arr[1], arr[2]), because we may be unsure how many there are. As our script executes, there could be a lot, or there could be none. And that would get ugly.undefinedundefined

    undefinedundefined

    undefinedundefinedSpread syntax to the rescue! It looks similar to rest parameters, also using undefinedundefined..., but does quite the opposite.undefinedundefined

    undefinedundefined

    When undefinedundefined...arr is used in the function call, it "expands" an iterable object undefinedundefinedarr into the list of arguments.undefinedundefined

    undefinedundefined

    For undefinedundefinedMath.max:undefinedundefined

    undefinedundefined

    run let arr = [3, 5, 1];

    undefinedundefined

    alert( Math.max(…arr) ); // 5 (spread turns array into a list of arguments)

    undefinedundefined

    We also can pass multiple iterables this way:

    undefinedundefined

    run let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1];

    undefinedundefined

    alert( Math.max(…arr1, …arr2) ); // 8

    undefinedundefined

    We can even combine the spread syntax with normal values:

    undefinedundefined

    run let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1];

    undefinedundefined

    alert( Math.max(1, …arr1, 2, …arr2, 25) ); // 25

    undefinedundefined

    Also, the spread syntax can be used to merge arrays:

    undefinedundefined

    run let arr = [3, 5, 1]; let arr2 = [8, 9, 15];

    undefinedundefined

    undefinedundefined! let merged = [0, …arr, 2, …arr2]; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)

    undefinedundefined

    In the examples above we used an array to demonstrate the spread syntax, but any iterable will do.

    undefinedundefined

    For instance, here we use the spread syntax to turn the string into array of characters:

    undefinedundefined

    run let str = "Hello";

    undefinedundefined

    alert( […str] ); // H,e,l,l,o

    undefinedundefined

    The spread syntax internally uses iterators to gather elements, the same way as undefinedundefinedfor..of does.undefinedundefined

    undefinedundefined

    So, for a string, undefinedundefinedfor..of returns characters and undefinedundefined...str becomes undefinedundefined"H","e","l","l","o". The list of characters is passed to array initializer undefinedundefined[...str].undefinedundefined

    undefinedundefined

    For this particular task we could also use undefinedundefinedArray.from, because it converts an iterable (like a string) into an array:undefinedundefined

    undefinedundefined

    run let str = "Hello";

    undefinedundefined

    // Array.from converts an iterable into an array alert( Array.from(str) ); // H,e,l,l,o

    undefinedundefined

    The result is the same as undefinedundefined[...str].undefinedundefined

    undefinedundefined

    But there's a subtle difference between undefinedundefinedArray.from(obj) and undefinedundefined[...obj]:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedArray.from operates on both array-likes and iterables.undefinedundefined
    • undefinedundefined
    • The spread syntax works only with iterables.
    • undefinedundefined
    undefinedundefined

    So, for the task of turning something into an array, undefinedundefinedArray.from tends to be more universal.undefinedundefined

    undefinedundefined

    Copy an array/object

    undefinedundefined

    Remember when we talked about undefinedundefinedObject.assign()undefinedundefinedin the past?undefinedundefined

    undefinedundefined

    It is possible to do the same thing with the spread syntax.

    undefinedundefined

    run let arr = [1, 2, 3];

    undefinedundefined

    undefinedundefined! let arrCopy = […arr]; // spread the array into a list of parameters // then put the result into a new array undefinedundefined/!undefinedundefined

    undefinedundefined

    // do the arrays have the same contents? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true

    undefinedundefined

    // are the arrays equal? alert(arr === arrCopy); // false (not same reference)

    undefinedundefined

    // modifying our initial array does not modify the copy: arr.push(4); alert(arr); // 1, 2, 3, 4 alert(arrCopy); // 1, 2, 3

    undefinedundefined

    Note that it is possible to do the same thing to make a copy of an object:

    undefinedundefined

    run let obj = { a: 1, b: 2, c: 3 };

    undefinedundefined

    undefinedundefined! let objCopy = { …obj }; // spread the object into a list of parameters // then return the result in a new object undefinedundefined/!undefinedundefined

    undefinedundefined

    // do the objects have the same contents? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true

    undefinedundefined

    // are the objects equal? alert(obj === objCopy); // false (not same reference)

    undefinedundefined

    // modifying our initial object does not modify the copy: obj.d = 4; alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}

    undefinedundefined

    This way of copying an object is much shorter than undefinedundefinedlet objCopy = Object.assign({}, obj) or for an array undefinedundefinedlet arrCopy = Object.assign([], arr) so we prefer to use it whenever we can.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    When we see undefinedundefined"..." in the code, it is either rest parameters or the spread syntax.undefinedundefined

    undefinedundefined

    There's an easy way to distinguish between them:

    undefinedundefined
      undefinedundefined
    • When undefinedundefined... is at the end of function parameters, it's "rest parameters" and gathers the rest of the list of arguments into an array.undefinedundefined
    • undefinedundefined
    • When undefinedundefined... occurs in a function call or alike, it's called a "spread syntax" and expands an array into a list.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Use patterns:

    undefinedundefined
      undefinedundefined
    • Rest parameters are used to create functions that accept any number of arguments.
    • undefinedundefined
    • The spread syntax is used to pass an array to functions that normally require a list of many arguments.
    • undefinedundefined
    undefinedundefined

    Together they help to travel between a list and an array of parameters with ease.

    undefinedundefined

    All arguments of a function call are also available in "old-style" undefinedundefinedarguments: array-like iterable object.undefinedundefined

    undefinedundefined

    Variable scope, closure

    undefinedundefined

    JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at any moment, passed as an argument to another function, and then called from a totally different place of code later.

    undefinedundefined

    We already know that a function can access variables outside of it ("outer" variables).

    undefinedundefined

    But what happens if outer variables change since a function is created? Will the function get newer values or the old ones?

    undefinedundefined

    And what if a function is passed along as a parameter and called from another place of code, will it get access to outer variables at the new place?

    undefinedundefined

    Let's expand our knowledge to understand these scenarios and more complex ones.

    undefinedundefined

    ``undefinedundefinedsmart header="We'll talk aboutlet/constundefinedundefinedvariables here" In JavaScript, there are 3 ways to declare a variable:letundefinedundefined,constundefinedundefined(the modern ones), andvar` (the remnant of the past).undefinedundefined

    undefinedundefined
      undefinedundefined
    • In this article we'll use undefinedundefinedlet variables in examples.undefinedundefined
    • undefinedundefined
    • Variables, declared with undefinedundefinedconst, behave the same, so this article is about undefinedundefinedconst too.undefinedundefined
    • undefinedundefined
    • The old undefinedundefinedvar has some notable differences, they will be covered in the article undefinedundefinedinfo:var. undefinedundefined
    • undefinedundefined
    undefinedundefined

    Code blocks

    undefinedundefined

    If a variable is declared inside a code block undefinedundefined{...}, it's only visible inside that block.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefined

    run { // do some job with local variables that should not be seen outside

    undefinedundefined

    let message = "Hello"; // only visible in this block

    undefinedundefined

    alert(message); // Hello }

    undefinedundefined

    alert(message); // Error: message is not defined

    undefinedundefined

    We can use this to isolate a piece of code that does its own task, with variables that only belong to it:

    undefinedundefined

    run { // show message let message = "Hello"; alert(message); }

    undefinedundefined

    { // show another message let message = "Goodbye"; alert(message); }

    undefinedundefined

    undefinedundefinedsmart header="There'd be an error without blocks" Please note, without separate blocks there would be an error, if we uselet` with the existing variable name:undefinedundefined

    undefinedundefined

    run // show message let message = "Hello"; alert(message);

    undefinedundefined

    // show another message undefinedundefined! let message = "Goodbye"; // Error: variable already declared undefinedundefined/! alert(message);undefinedundefined

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    For undefinedundefinedif, undefinedundefinedfor, undefinedundefinedwhile and so on, variables declared in undefinedundefined{...} are also only visible inside:undefinedundefined

    undefinedundefined

    run if (true) { let phrase = "Hello!";

    undefinedundefined

    alert(phrase); // Hello! }

    undefinedundefined

    alert(phrase); // Error, no such variable!

    undefinedundefined

    Here, after undefinedundefinedif finishes, the undefinedundefinedalert below won't see the undefinedundefinedphrase, hence the error.undefinedundefined

    undefinedundefined

    That's great, as it allows us to create block-local variables, specific to an undefinedundefinedif branch.undefinedundefined

    undefinedundefined

    The similar thing holds true for undefinedundefinedfor and undefinedundefinedwhile loops:undefinedundefined

    undefinedundefined

    run for (let i = 0; i < 3; i++) { // the variable i is only visible inside this for alert(i); // 0, then 1, then 2 }

    undefinedundefined

    alert(i); // Error, no such variable

    undefinedundefined

    Visually, undefinedundefinedlet i is outside of undefinedundefined{...}. But the undefinedundefinedfor construct is special here: the variable, declared inside it, is considered a part of the block.undefinedundefined

    undefinedundefined

    Nested functions

    undefinedundefined

    A function is called "nested" when it is created inside another function.

    undefinedundefined

    It is easily possible to do this with JavaScript.

    undefinedundefined

    We can use it to organize our code, like this:

    undefinedundefinedundefinedundefined

    Here the undefinedundefinednested function undefinedundefinedgetFullName() is made for convenience. It can access the outer variables and so can return the full name. Nested functions are quite common in JavaScript.undefinedundefined

    undefinedundefined

    What's much more interesting, a nested function can be returned: either as a property of a new object or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables.

    undefinedundefined

    Below, undefinedundefinedmakeCounter creates the "counter" function that returns the next number on each invocation:undefinedundefined

    undefinedundefined

    run function makeCounter() { let count = 0;

    undefinedundefined

    return function() { return count++; }; }

    undefinedundefined

    let counter = makeCounter();

    undefinedundefined

    alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2

    undefinedundefined

    Despite being simple, slightly modified variants of that code have practical uses, for instance, as a undefinedundefinedrandom number generator to generate random values for automated tests.undefinedundefined

    undefinedundefined

    How does this work? If we create multiple counters, will they be independent? What's going on with the variables here?

    undefinedundefined

    Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth.

    undefinedundefined

    Lexical Environment

    undefinedundefined

    warn header="Here be dragons!" The in-depth technical explanation lies ahead.

    undefinedundefined

    As far as I'd like to avoid low-level language details, any understanding without them would be lacking and incomplete, so get ready.

    undefinedundefined

    For clarity, the explanation is split into multiple steps.

    undefinedundefined

    Step 1. Variables

    undefinedundefined

    In JavaScript, every running function, code block undefinedundefined{...}, and the script as a whole have an internal (hidden) associated object known as the undefinedundefinedLexical Environment.undefinedundefined

    undefinedundefined

    The Lexical Environment object consists of two parts:

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedEnvironment Record - an object that stores all local variables as its properties (and some other information like the value of undefinedundefinedthis).undefinedundefined
    2. undefinedundefined
    3. A reference to the undefinedundefinedouter lexical environment, the one associated with the outer code.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    undefinedundefinedA "variable" is just a property of the special internal object, undefinedundefinedEnvironment Record. "To get or change a variable" means "to get or change a property of that object".undefinedundefinedundefinedundefined

    undefinedundefined

    In this simple code without functions, there is only one Lexical Environment:

    undefinedundefined
    undefinedundefinedlexical environmentundefinedundefined
    lexical environment
    undefinedundefined
    undefinedundefined

    This is the so-called undefinedundefinedglobal Lexical Environment, associated with the whole script.undefinedundefined

    undefinedundefined

    On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, that's why the arrow points to undefinedundefinednull.undefinedundefined

    undefinedundefined

    As the code starts executing and goes on, the Lexical Environment changes.

    undefinedundefined

    Here's a little bit longer code:

    undefinedundefined
    undefinedundefinedlexical environmentundefinedundefined
    lexical environment
    undefinedundefined
    undefinedundefined

    Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution:

    undefinedundefined
      undefinedundefined
    1. When the script starts, the Lexical Environment is pre-populated with all declared variables. undefinedundefined
        undefinedundefined
      • Initially, they are in the "Uninitialized" state. That's a special internal state, it means that the engine knows about the variable, but it cannot be referenced until it has been declared with undefinedundefinedlet. It's almost the same as if the variable didn't exist.undefinedundefined
      • undefinedundefined
      undefinedundefined
    2. undefinedundefined
    3. Then undefinedundefinedlet phrase definition appears. There's no assignment yet, so its value is undefinedundefinedundefined. We can use the variable from this point forward.undefinedundefined
    4. undefinedundefined
    5. undefinedundefinedphrase is assigned a value.undefinedundefined
    6. undefinedundefined
    7. undefinedundefinedphrase changes the value.undefinedundefined
    8. undefinedundefined
    undefinedundefined

    Everything looks simple for now, right?

    undefinedundefined
      undefinedundefined
    • A variable is a property of a special internal object, associated with the currently executing block/function/script.
    • undefinedundefined
    • Working with variables is actually working with the properties of that object.
    • undefinedundefined
    undefinedundefined

    smart header="Lexical Environment is a specification object" "Lexical Environment" is a specification object: it only exists "theoretically" in the undefinedundefinedlanguage specification to describe how things work. We can't get this object in our code and manipulate it directly.undefinedundefined

    undefinedundefined

    JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described.

    undefinedundefined

    Step 2. Function Declarations

    undefinedundefined

    A function is also a value, like a variable.

    undefinedundefined

    undefinedundefinedThe difference is that a Function Declaration is instantly fully initialized.undefinedundefined

    undefinedundefined

    When a Lexical Environment is created, a Function Declaration immediately becomes a ready-to-use function (unlike undefinedundefinedlet, that is unusable till the declaration).undefinedundefined

    undefinedundefined

    That's why we can use a function, declared as Function Declaration, even before the declaration itself.

    undefinedundefined

    For example, here's the initial state of the global Lexical Environment when we add a function:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable, such as undefinedundefinedlet say = function(name)....undefinedundefined

    undefinedundefined

    Step 3. Inner and outer Lexical Environment

    undefinedundefined

    When a function runs, at the beginning of the call, a new Lexical Environment is created automatically to store local variables and parameters of the call.

    undefinedundefined

    For instance, for undefinedundefinedsay("John"), it looks like this (the execution is at the line, labelled with an arrow):undefinedundefined

    undefinedundefined undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global):

    undefinedundefined
      undefinedundefined
    • The inner Lexical Environment corresponds to the current execution of undefinedundefinedsay. It has a single property: undefinedundefinedname, the function argument. We called undefinedundefinedsay("John"), so the value of the undefinedundefinedname is undefinedundefined"John".undefinedundefined
    • undefinedundefined
    • The outer Lexical Environment is the global Lexical Environment. It has the undefinedundefinedphrase variable and the function itself.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The inner Lexical Environment has a reference to the undefinedundefinedouter one.undefinedundefined

    undefinedundefined

    undefinedundefinedWhen the code wants to access a variable - the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.undefinedundefined

    undefinedundefined

    If a variable is not found anywhere, that's an error in strict mode (without undefinedundefineduse strict, an assignment to a non-existing variable creates a new global variable, for compatibility with old code).undefinedundefined

    undefinedundefined

    In this example the search proceeds as follows:

    undefinedundefined
      undefinedundefined
    • For the undefinedundefinedname variable, the undefinedundefinedalert inside undefinedundefinedsay finds it immediately in the inner Lexical Environment.undefinedundefined
    • undefinedundefined
    • When it wants to access undefinedundefinedphrase, then there is no undefinedundefinedphrase locally, so it follows the reference to the outer Lexical Environment and finds it there.undefinedundefined
    • undefinedundefined
    undefinedundefined
    undefinedundefinedlexical environment lookupundefinedundefined
    lexical environment lookup
    undefinedundefined
    undefinedundefined

    Step 4. Returning a function

    undefinedundefined

    Let's return to the undefinedundefinedmakeCounter example.undefinedundefined

    undefinedundefinedundefinedundefined

    At the beginning of each undefinedundefinedmakeCounter() call, a new Lexical Environment object is created, to store variables for this undefinedundefinedmakeCounter run.undefinedundefined

    undefinedundefined

    So we have two nested Lexical Environments, just like in the example above:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    What's different is that, during the execution of undefinedundefinedmakeCounter(), a tiny nested function is created of only one line: undefinedundefinedreturn count++. We don't run it yet, only create.undefinedundefined

    undefinedundefined

    All functions remember the Lexical Environment in which they were made. Technically, there's no magic here: all functions have the hidden property named undefinedundefined[[Environment]], that keeps the reference to the Lexical Environment where the function was created:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    So, undefinedundefinedcounter.[[Environment]] has the reference to undefinedundefined{count: 0} Lexical Environment. That's how the function remembers where it was created, no matter where it's called. The undefinedundefined[[Environment]] reference is set once and forever at function creation time.undefinedundefined

    undefinedundefined

    Later, when undefinedundefinedcounter() is called, a new Lexical Environment is created for the call, and its outer Lexical Environment reference is taken from undefinedundefinedcounter.[[Environment]]:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Now when the code inside undefinedundefinedcounter() looks for undefinedundefinedcount variable, it first searches its own Lexical Environment (empty, as there are no local variables there), then the Lexical Environment of the outer undefinedundefinedmakeCounter() call, where it finds and changes it.undefinedundefined

    undefinedundefined

    undefinedundefinedA variable is updated in the Lexical Environment where it lives.undefinedundefined

    undefinedundefined

    Here's the state after the execution:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    If we call undefinedundefinedcounter() multiple times, the undefinedundefinedcount variable will be increased to undefinedundefined2, undefinedundefined3 and so on, at the same place.undefinedundefined

    undefinedundefined

    smart header="Closure" There is a general programming term "closure", that developers generally should know.

    undefinedundefined

    A undefinedundefinedclosure is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in undefinedundefinedinfo:new-function).undefinedundefined

    undefinedundefined

    That is: they automatically remember where they were created using a hidden undefinedundefined[[Environment]] property, and then their code can access outer variables.undefinedundefined

    undefinedundefined

    When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the undefinedundefined[[Environment]] property and how Lexical Environments work. undefinedundefined

    undefinedundefined

    Garbage collection

    undefinedundefined

    Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable.

    undefinedundefined

    However, if there's a nested function that is still reachable after the end of a function, then it has undefinedundefined[[Environment]] property that references the lexical environment.undefinedundefined

    undefinedundefined

    In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive.

    undefinedundefined

    For example:

    undefinedundefinedundefinedundefined

    Please note that if undefinedundefinedf() is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them:undefinedundefined

    undefinedundefinedundefinedundefined

    A Lexical Environment object dies when it becomes unreachable (just like any other object). In other words, it exists only while there's at least one nested function referencing it.

    undefinedundefined

    In the code below, after the nested function is removed, its enclosing Lexical Environment (and hence the undefinedundefinedvalue) is cleaned from memory:undefinedundefined

    undefinedundefinedundefinedundefined

    Real-life optimizations

    undefinedundefined

    As we've seen, in theory while a function is alive, all outer variables are also retained.

    undefinedundefined

    But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's obvious from the code that an outer variable is not used - it is removed.

    undefinedundefined

    undefinedundefinedAn important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging.undefinedundefined

    undefinedundefined

    Try running the example below in Chrome with the Developer Tools open.

    undefinedundefined

    When it pauses, in the console type undefinedundefinedalert(value).undefinedundefined

    undefinedundefined

    run function f() { let value = Math.random();

    undefinedundefined

    function g() { debugger; // in console: type alert(value); No such variable! }

    undefinedundefined

    return g; }

    undefinedundefined

    let g = f(); g();

    undefinedundefined

    As you could see - there is no such variable! In theory, it should be accessible, but the engine optimized it out.

    undefinedundefined

    That may lead to funny (if not such time-consuming) debugging issues. One of them - we can see a same-named outer variable instead of the expected one:

    undefinedundefined

    run global let value = "Surprise!";

    undefinedundefined

    function f() { let value = "the closest value";

    undefinedundefined

    function g() { debugger; // in console: type alert(value); Surprise! }

    undefinedundefined

    return g; }

    undefinedundefined

    let g = f(); g();

    undefinedundefined

    This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it.

    undefinedundefined

    That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page.

    undefinedundefined

    The old "var"

    undefinedundefined

    smart header="This article is for understanding old scripts" The information in this article is useful for understanding old scripts.

    undefinedundefined

    That's not how we write a new code.

    undefinedundefined

    In the very first chapter about undefinedundefinedvariables, we mentioned three ways of variable declaration:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedletundefinedundefined
    2. undefinedundefined
    3. undefinedundefinedconstundefinedundefined
    4. undefinedundefined
    5. undefinedundefinedvarundefinedundefined
    6. undefinedundefined
    undefinedundefined

    The undefinedundefinedvar declaration is similar to undefinedundefinedlet. Most of the time we can replace undefinedundefinedlet by undefinedundefinedvar or vice-versa and expect things to work:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run var message = "Hi"; alert(message); // Hiundefinedundefined

    undefinedundefined

    But internally undefinedundefinedvar is a very different beast, that originates from very old times. It's generally not used in modern scripts, but still lurks in the old ones.undefinedundefined

    undefinedundefined

    If you don't plan on meeting such scripts you may even skip this chapter or postpone it.

    undefinedundefined

    On the other hand, it's important to understand differences when migrating old scripts from undefinedundefinedvar to undefinedundefinedlet, to avoid odd errors.undefinedundefined

    undefinedundefined

    "var" has no block scope

    undefinedundefined

    Variables, declared with undefinedundefinedvar, are either function-scoped or global-scoped. They are visible through blocks.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run if (true) { var test = true; // use "var" instead of "let" }

    undefinedundefined

    undefinedundefined! alert(test); // true, the variable lives after if undefinedundefined/! undefinedundefined

    undefinedundefined

    As undefinedundefinedvar ignores code blocks, we've got a global variable undefinedundefinedtest.undefinedundefined

    undefinedundefined

    If we used undefinedundefinedlet test instead of undefinedundefinedvar test, then the variable would only be visible inside undefinedundefinedif:undefinedundefined

    undefinedundefined

    run if (true) { let test = true; // use "let" }

    undefinedundefined

    undefinedundefined! alert(test); // ReferenceError: test is not defined undefinedundefined/! undefinedundefined

    undefinedundefined

    The same thing for loops: undefinedundefinedvar cannot be block- or loop-local:undefinedundefined

    undefinedundefinedundefinedundefined

    If a code block is inside a function, then undefinedundefinedvar becomes a function-level variable:undefinedundefined

    undefinedundefined

    run function sayHi() { if (true) { var phrase = "Hello"; }

    undefinedundefined

    alert(phrase); // works }

    undefinedundefined

    sayHi(); alert(phrase); // ReferenceError: phrase is not defined

    undefinedundefined

    As we can see, undefinedundefinedvar pierces through undefinedundefinedif, undefinedundefinedfor or other code blocks. That's because a long time ago in JavaScript, blocks had no Lexical Environments, and undefinedundefinedvar is a remnant of that.undefinedundefined

    undefinedundefined

    "var" tolerates redeclarations

    undefinedundefined

    If we declare the same variable with undefinedundefinedlet twice in the same scope, that's an error:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let user; let user; // SyntaxError: 'user' has already been declaredundefinedundefined

    undefinedundefined

    With undefinedundefinedvar, we can redeclare a variable any number of times. If we use undefinedundefinedvar with an already-declared variable, it's just ignored:undefinedundefined

    undefinedundefined

    run var user = "Pete";

    undefinedundefined

    var user = "John"; // this "var" does nothing (already declared) // …it doesn't trigger an error

    undefinedundefined

    alert(user); // John

    undefinedundefined

    "var" variables can be declared below their use

    undefinedundefined

    undefinedundefinedvar declarations are processed when the function starts (or script starts for globals).undefinedundefined

    undefinedundefined

    In other words, undefinedundefinedvar variables are defined from the beginning of the function, no matter where the definition is (assuming that the definition is not in the nested function).undefinedundefined

    undefinedundefined

    So this code:

    undefinedundefined

    run function sayHi() { phrase = "Hello";

    undefinedundefined

    alert(phrase);

    undefinedundefined

    undefinedundefined! var phrase; undefinedundefined/! } sayHi(); undefinedundefined

    undefinedundefined

    …Is technically the same as this (moved undefinedundefinedvar phrase above):undefinedundefined

    undefinedundefined

    run function sayHi() { undefinedundefined! var phrase; undefinedundefined/!undefinedundefined

    undefinedundefined

    phrase = "Hello";

    undefinedundefined

    alert(phrase); } sayHi();

    undefinedundefined

    …Or even as this (remember, code blocks are ignored):

    undefinedundefined

    run function sayHi() { phrase = "Hello"; // (*)

    undefinedundefined

    undefinedundefined! if (false) { var phrase; } undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(phrase); } sayHi();

    undefinedundefined

    People also call such behavior "hoisting" (raising), because all undefinedundefinedvar are "hoisted" (raised) to the top of the function.undefinedundefined

    undefinedundefined

    So in the example above, undefinedundefinedif (false) branch never executes, but that doesn't matter. The undefinedundefinedvar inside it is processed in the beginning of the function, so at the moment of undefinedundefined(*) the variable exists.undefinedundefined

    undefinedundefined

    undefinedundefinedDeclarations are hoisted, but assignments are not.undefinedundefined

    undefinedundefined

    That's best demonstrated with an example:

    undefinedundefined

    run function sayHi() { alert(phrase);

    undefinedundefined

    undefinedundefined! var phrase = "Hello"; undefinedundefined/! }undefinedundefined

    undefinedundefined

    sayHi();

    undefinedundefined

    The line undefinedundefinedvar phrase = "Hello" has two actions in it:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Variable declaration undefinedundefinedvarundefinedundefined
    2. undefinedundefined
    3. Variable assignment undefinedundefined=.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    The declaration is processed at the start of function execution ("hoisted"), but the assignment always works at the place where it appears. So the code works essentially like this:

    undefinedundefined

    run function sayHi() { undefinedundefined! var phrase; // declaration works at the start… undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(phrase); // undefined

    undefinedundefined

    undefinedundefined! phrase = "Hello"; // …assignment - when the execution reaches it. undefinedundefined/! }undefinedundefined

    undefinedundefined

    sayHi();

    undefinedundefined

    Because all undefinedundefinedvar declarations are processed at the function start, we can reference them at any place. But variables are undefined until the assignments.undefinedundefined

    undefinedundefined

    In both examples above, undefinedundefinedalert runs without an error, because the variable undefinedundefinedphrase exists. But its value is not yet assigned, so it shows undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    IIFE

    undefinedundefined

    In the past, as there was only undefinedundefinedvar, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE).undefinedundefined

    undefinedundefined

    That's not something we should use nowadays, but you can find them in old scripts.

    undefinedundefined

    An IIFE looks like this:

    undefinedundefined

    run (function() {

    undefinedundefined

    var message = "Hello";

    undefinedundefined

    alert(message); // Hello

    undefinedundefined

    })();

    undefinedundefined

    Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables.

    undefinedundefined

    The Function Expression is wrapped with parenthesis undefinedundefined(function {...}), because when JavaScript engine encounters undefinedundefined"function" in the main code, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error:undefinedundefined

    undefinedundefined

    run // Tries to declare and immediately call a function function() { // <- SyntaxError: Function statements require a function name

    undefinedundefined

    var message = "Hello";

    undefinedundefined

    alert(message); // Hello

    undefinedundefined

    }();

    undefinedundefined

    Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately:

    undefinedundefined

    run // syntax error because of parentheses below function go() {

    undefinedundefined

    }(); // <- can't call Function Declaration immediately

    undefinedundefined

    So, the parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately.

    undefinedundefined

    There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression:

    undefinedundefined

    run // Ways to create IIFE

    undefinedundefined

    (function() { alert("Parentheses around the function"); }undefinedundefined!)undefinedundefined/!();undefinedundefined

    undefinedundefined

    (function() { alert("Parentheses around the whole thing"); }()undefinedundefined!)undefinedundefined/!;undefinedundefined

    undefinedundefined

    undefinedundefined!!undefinedundefined/!function() { alert("Bitwise NOT operator starts the expression"); }();undefinedundefined

    undefinedundefined

    undefinedundefined!+undefinedundefined/!function() { alert("Unary plus starts the expression"); }(); undefinedundefined

    undefinedundefined

    In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code.

    undefinedundefined

    Summary

    undefinedundefined

    There are two main differences of undefinedundefinedvar compared to undefinedundefinedlet/const:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedvar variables have no block scope, their visibility is scoped to current function, or global, if declared outside function.undefinedundefined
    2. undefinedundefined
    3. undefinedundefinedvar declarations are processed at function start (script start for globals).undefinedundefined
    4. undefinedundefined
    undefinedundefined

    There's one more very minor difference related to the global object, that we'll cover in the next chapter.

    undefinedundefined

    These differences make undefinedundefinedvar worse than undefinedundefinedlet most of the time. Block-level variables is such a great thing. That's why undefinedundefinedlet was introduced in the standard long ago, and is now a major way (along with undefinedundefinedconst) to declare a variable.undefinedundefined

    undefinedundefined

    Global object

    undefinedundefined

    The global object provides variables and functions that are available anywhere. By default, those that are built into the language or the environment.

    undefinedundefined

    In a browser it is named undefinedundefinedwindow, for Node.js it is undefinedundefinedglobal, for other environments it may have another name.undefinedundefined

    undefinedundefined

    Recently, undefinedundefinedglobalThis was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers.undefinedundefined

    undefinedundefined

    We'll use undefinedundefinedwindow here, assuming that our environment is a browser. If your script may run in other environments, it's better to use undefinedundefinedglobalThis instead.undefinedundefined

    undefinedundefined

    All properties of the global object can be accessed directly:

    undefinedundefined

    undefinedundefinedjs run alert("Hello"); // is the same as window.alert("Hello");undefinedundefined

    undefinedundefined

    In a browser, global functions and variables declared with undefinedundefinedvar (not undefinedundefinedlet/const!) become the property of the global object:undefinedundefined

    undefinedundefined

    run untrusted refresh var gVar = 5;

    undefinedundefined

    alert(window.gVar); // 5 (became a property of the global object)

    undefinedundefined

    The same effect have function declarations (statements with undefinedundefinedfunction keyword in the main code flow, not function expressions).undefinedundefined

    undefinedundefined

    Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use undefinedundefinedJavaScript modules where such a thing doesn't happen.undefinedundefined

    undefinedundefined

    If we used undefinedundefinedlet instead, such thing wouldn't happen:undefinedundefined

    undefinedundefined

    run untrusted refresh let gLet = 5;

    undefinedundefined

    alert(window.gLet); // undefined (doesn't become a property of the global object)

    undefinedundefined

    If a value is so important that you'd like to make it available globally, write it directly as a property:

    undefinedundefined

    run undefinedundefined! // make current user information global, to let all scripts access it window.currentUser = { name: "John" }; undefinedundefined/!undefinedundefined

    undefinedundefined

    // somewhere else in code alert(currentUser.name); // John

    undefinedundefined

    // or, if we have a local variable with the name "currentUser" // get it from window explicitly (safe!) alert(window.currentUser.name); // John

    undefinedundefined

    That said, using global variables is generally discouraged. There should be as few global variables as possible. The code design where a function gets "input" variables and produces certain "outcome" is clearer, less prone to errors and easier to test than if it uses outer or global variables.

    undefinedundefined

    Using for polyfills

    undefinedundefined

    We use the global object to test for support of modern language features.

    undefinedundefined

    For instance, test if a built-in undefinedundefinedPromise object exists (it doesn't in really old browsers): undefinedundefinedjs run if (!window.Promise) { alert("Your browser is really old!"); }undefinedundefined

    undefinedundefined

    If there's none (say, we're in an old browser), we can create "polyfills": add functions that are not supported by the environment, but exist in the modern standard.

    undefinedundefined

    undefinedundefinedjs run if (!window.Promise) { window.Promise = ... // custom implementation of the modern language feature }undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      The global object holds variables that should be available everywhere.

      That includes JavaScript built-ins, such as undefinedundefinedArray and environment-specific values, such as undefinedundefinedwindow.innerHeight - the window height in the browser.undefinedundefined
    • undefinedundefined
    • undefinedundefined

      The global object has a universal name undefinedundefinedglobalThis.undefinedundefined

      …But more often is referred by "old-school" environment-specific names, such as undefinedundefinedwindow (browser) and undefinedundefinedglobal (Node.js).undefinedundefined
    • undefinedundefined
    • We should store values in the global object only if they're truly global for our project. And keep their number at minimum.
    • undefinedundefined
    • In-browser, unless we're using undefinedundefinedmodules, global functions and variables declared with undefinedundefinedvar become a property of the global object.undefinedundefined
    • undefinedundefined
    • undefinedundefined

      To make our code future-proof and easier to understand, we should access properties of the global object directly, as undefinedundefinedwindow.x.undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Function object, NFE

    undefinedundefined

    As we already know, a function in JavaScript is a value.

    undefinedundefined

    Every value in JavaScript has a type. What type is a function?

    undefinedundefined

    In JavaScript, functions are objects.

    undefinedundefined

    A good way to imagine functions is as callable "action objects". We can not only call them, but also treat them as objects: add/remove properties, pass by reference etc.

    undefinedundefined

    The "name" property

    undefinedundefined

    Function objects contain some useable properties.

    undefinedundefined

    For instance, a function's name is accessible as the "name" property:

    undefinedundefined

    run function sayHi() { alert("Hi"); }

    undefinedundefined

    alert(sayHi.name); // sayHi

    undefinedundefined

    What's kind of funny, the name-assigning logic is smart. It also assigns the correct name to a function even if it's created without one, and then immediately assigned:

    undefinedundefined

    run let sayHi = function() { alert("Hi"); };

    undefinedundefined

    alert(sayHi.name); // sayHi (there's a name!)

    undefinedundefined

    It also works if the assignment is done via a default value:

    undefinedundefined

    run function f(sayHi = function() {}) { alert(sayHi.name); // sayHi (works!) }

    undefinedundefined

    f();

    undefinedundefined

    In the specification, this feature is called a "contextual name". If the function does not provide one, then in an assignment it is figured out from the context.

    undefinedundefined

    Object methods have names too:

    undefinedundefined

    run let user = {

    undefinedundefined

    sayHi() { // … },

    undefinedundefined

    sayBye: function() { // … }

    undefinedundefined

    }

    undefinedundefined

    alert(user.sayHi.name); // sayHi alert(user.sayBye.name); // sayBye

    undefinedundefined

    There's no magic though. There are cases when there's no way to figure out the right name. In that case, the name property is empty, like here:

    undefinedundefined

    run // function created inside array let arr = [function() {}];

    undefinedundefined

    alert( arr[0].name ); // undefinedundefined // the engine has no way to set up the right name, so there is none undefinedundefined

    undefinedundefined

    In practice, however, most functions do have a name.

    undefinedundefined

    The "length" property

    undefinedundefined

    There is another built-in property "length" that returns the number of function parameters, for instance:

    undefinedundefined

    run function f1(a) {} function f2(a, b) {} function many(a, b, …more) {}

    undefinedundefined

    alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2

    undefinedundefined

    Here we can see that rest parameters are not counted.

    undefinedundefined

    The undefinedundefinedlength property is sometimes used for undefinedundefinedintrospection in functions that operate on other functions.undefinedundefined

    undefinedundefined

    For instance, in the code below the undefinedundefinedask function accepts a undefinedundefinedquestion to ask and an arbitrary number of undefinedundefinedhandler functions to call.undefinedundefined

    undefinedundefined

    Once a user provides their answer, the function calls the handlers. We can pass two kinds of handlers:

    undefinedundefined
      undefinedundefined
    • A zero-argument function, which is only called when the user gives a positive answer.
    • undefinedundefined
    • A function with arguments, which is called in either case and returns an answer.
    • undefinedundefined
    undefinedundefined

    To call undefinedundefinedhandler the right way, we examine the undefinedundefinedhandler.length property.undefinedundefined

    undefinedundefined

    The idea is that we have a simple, no-arguments handler syntax for positive cases (most frequent variant), but are able to support universal handlers as well:

    undefinedundefined

    run function ask(question, …handlers) { let isYes = confirm(question);

    undefinedundefined

    for(let handler of handlers) { if (handler.length == 0) { if (isYes) handler(); } else { handler(isYes); } }

    undefinedundefined

    }

    undefinedundefined

    // for positive answer, both handlers are called // for negative answer, only the second one ask("Question?", () => alert(‘You said yes'), result => alert(result));

    undefinedundefined

    This is a particular case of so-called undefinedundefinedpolymorphism - treating arguments differently depending on their type or, in our case depending on the undefinedundefinedlength. The idea does have a use in JavaScript libraries.undefinedundefined

    undefinedundefined

    Custom properties

    undefinedundefined

    We can also add properties of our own.

    undefinedundefined

    Here we add the undefinedundefinedcounter property to track the total calls count:undefinedundefined

    undefinedundefined

    run function sayHi() { alert("Hi");

    undefinedundefined

    undefinedundefined! // let's count how many times we run sayHi.counter++; undefinedundefined/! } sayHi.counter = 0; // initial valueundefinedundefined

    undefinedundefined

    sayHi(); // Hi sayHi(); // Hi

    undefinedundefined

    alert( undefinedundefinedCalled ${sayHi.counter} times ); // Called 2 times undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="A property is not a variable" A property assigned to a function likesayHi.counter = 0undefinedundefineddoes *not* define a local variablecounterundefinedundefinedinside it. In other words, a propertycounterundefinedundefinedand a variablelet counter` are two unrelated things.undefinedundefined

    undefinedundefined

    We can treat a function as an object, store properties in it, but that has no effect on its execution. Variables are not function properties and vice versa. These are just parallel worlds.

    undefinedundefined

    Function properties can replace closures sometimes. For instance, we can rewrite the counter function example from the chapter undefinedundefinedinfo:closure to use a function property:undefinedundefined

    undefinedundefined

    run function makeCounter() { // instead of: // let count = 0

    undefinedundefined

    function counter() { return counter.count++; };

    undefinedundefined

    counter.count = 0;

    undefinedundefined

    return counter; }

    undefinedundefined

    let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1

    undefinedundefined

    The undefinedundefinedcount is now stored in the function directly, not in its outer Lexical Environment.undefinedundefined

    undefinedundefined

    Is it better or worse than using a closure?

    undefinedundefined

    The main difference is that if the value of undefinedundefinedcount lives in an outer variable, then external code is unable to access it. Only nested functions may modify it. And if it's bound to a function, then such a thing is possible:undefinedundefined

    undefinedundefined

    run function makeCounter() {

    undefinedundefined

    function counter() { return counter.count++; };

    undefinedundefined

    counter.count = 0;

    undefinedundefined

    return counter; }

    undefinedundefined

    let counter = makeCounter();

    undefinedundefined

    undefinedundefined! counter.count = 10; alert( counter() ); // 10 undefinedundefined/! undefinedundefined

    undefinedundefined

    So the choice of implementation depends on our aims.

    undefinedundefined

    Named Function Expression

    undefinedundefined

    Named Function Expression, or NFE, is a term for Function Expressions that have a name.

    undefinedundefined

    For instance, let's take an ordinary Function Expression:

    undefinedundefinedundefinedundefined

    And add a name to it:

    undefinedundefinedundefinedundefined

    Did we achieve anything here? What's the purpose of that additional undefinedundefined"func" name?undefinedundefined

    undefinedundefined

    First let's note, that we still have a Function Expression. Adding the name undefinedundefined"func" after undefinedundefinedfunction did not make it a Function Declaration, because it is still created as a part of an assignment expression.undefinedundefined

    undefinedundefined

    Adding such a name also did not break anything.

    undefinedundefined

    The function is still available as undefinedundefinedsayHi():undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let sayHi = function *!*func*/!*(who) { alert(Hello, ${who}`); };undefinedundefined

    undefinedundefined

    sayHi("John"); // Hello, John

    undefinedundefined

    There are two special things about the name undefinedundefinedfunc, that are the reasons for it:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. It allows the function to reference itself internally.
    2. undefinedundefined
    3. It is not visible outside of the function.
    4. undefinedundefined
    undefinedundefined

    For instance, the function undefinedundefinedsayHi below calls itself again with undefinedundefined"Guest" if no undefinedundefinedwho is provided:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let sayHi = function *!*func*/!*(who) { if (who) { alert(Hello, ${who}`); } else { undefinedundefined! func("Guest"); // use func to re-call itself undefinedundefined/! } };undefinedundefined

    undefinedundefined

    sayHi(); // Hello, Guest

    undefinedundefined

    // But this won't work: func(); // Error, func is not defined (not visible outside of the function)

    undefinedundefined

    Why do we use undefinedundefinedfunc? Maybe just use undefinedundefinedsayHi for the nested call?undefinedundefined

    undefinedundefined

    Actually, in most cases we can:

    undefinedundefinedundefinedundefined

    The problem with that code is that undefinedundefinedsayHi may change in the outer code. If the function gets assigned to another variable instead, the code will start to give errors:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let sayHi = function(who) { if (who) { alert(Hello, ${who}`); } else { undefinedundefined! sayHi("Guest"); // Error: sayHi is not a function undefinedundefined/! } };undefinedundefined

    undefinedundefined

    let welcome = sayHi; sayHi = null;

    undefinedundefined

    welcome(); // Error, the nested sayHi call doesn't work any more!

    undefinedundefined

    That happens because the function takes undefinedundefinedsayHi from its outer lexical environment. There's no local undefinedundefinedsayHi, so the outer variable is used. And at the moment of the call that outer undefinedundefinedsayHi is undefinedundefinednull.undefinedundefined

    undefinedundefined

    The optional name which we can put into the Function Expression is meant to solve exactly these kinds of problems.

    undefinedundefined

    Let's use it to fix our code:

    undefinedundefined

    ``undefinedundefinedjs run let sayHi = function *!*func*/!*(who) { if (who) { alert(Hello, ${who}`); } else { undefinedundefined! func("Guest"); // Now all fine undefinedundefined/! } };undefinedundefined

    undefinedundefined

    let welcome = sayHi; sayHi = null;

    undefinedundefined

    welcome(); // Hello, Guest (nested call works)

    undefinedundefined

    Now it works, because the name undefinedundefined"func" is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function.undefinedundefined

    undefinedundefined

    The outer code still has its variable undefinedundefinedsayHi or undefinedundefinedwelcome. And undefinedundefinedfunc is an "internal function name", how the function can call itself internally.undefinedundefined

    undefinedundefined

    smart header="There's no such thing for Function Declaration" The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name.

    undefinedundefined

    Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form.

    undefinedundefined

    Summary

    undefinedundefined

    Functions are objects.

    undefinedundefined

    Here we covered their properties:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedname - the function name. Usually taken from the function definition, but if there's none, JavaScript tries to guess it from the context (e.g. an assignment).undefinedundefined
    • undefinedundefined
    • undefinedundefinedlength - the number of arguments in the function definition. Rest parameters are not counted.undefinedundefined
    • undefinedundefined
    undefinedundefined

    If the function is declared as a Function Expression (not in the main code flow), and it carries the name, then it is called a Named Function Expression. The name can be used inside to reference itself, for recursive calls or such.

    undefinedundefined

    Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature.

    undefinedundefined

    They create a "main" function and attach many other "helper" functions to it. For instance, the undefinedundefinedjQuery library creates a function named undefinedundefined$. The undefinedundefinedlodash library creates a function undefinedundefined_, and then adds undefinedundefined_.clone, undefinedundefined_.keyBy and other properties to it (see the undefinedundefineddocs when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.undefinedundefined

    undefinedundefined

    So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.

    undefinedundefined

    The "new Function" syntax

    undefinedundefined

    There's one more way to create a function. It's rarely used, but sometimes there's no alternative.

    undefinedundefined

    Syntax

    undefinedundefined

    The syntax for creating a function:

    undefinedundefinedundefinedundefined

    The function is created with the arguments undefinedundefinedarg1...argN and the given undefinedundefinedfunctionBody.undefinedundefined

    undefinedundefined

    It's easier to understand by looking at an example. Here's a function with two arguments:

    undefinedundefined

    run let sum = new Function(‘a', ‘b', ‘return a + b');

    undefinedundefined

    alert( sum(1, 2) ); // 3

    undefinedundefined

    And here there's a function without arguments, with only the function body:

    undefinedundefined

    run let sayHi = new Function(‘alert("Hello")''');

    undefinedundefined

    sayHi(); // Hello

    undefinedundefined

    The major difference from other ways we've seen is that the function is created literally from a string, that is passed at run time.

    undefinedundefined

    All previous declarations required us, programmers, to write the function code in the script.

    undefinedundefined

    But undefinedundefinednew Function allows to turn any string into a function. For example, we can receive a new function from a server and then execute it:undefinedundefined

    undefinedundefinedundefinedundefined

    It is used in very specific cases, like when we receive code from a server, or to dynamically compile a function from a template, in complex web-applications.

    undefinedundefined

    Closure

    undefinedundefined

    Usually, a function remembers where it was born in the special property undefinedundefined[[Environment]]. It references the Lexical Environment from where it's created (we covered that in the chapter undefinedundefinedinfo:closure).undefinedundefined

    undefinedundefined

    But when a function is created using undefinedundefinednew Function, its undefinedundefined[[Environment]] is set to reference not the current Lexical Environment, but the global one.undefinedundefined

    undefinedundefined

    So, such function doesn't have access to outer variables, only to the global ones.

    undefinedundefined

    run function getFunc() { let value = "test";

    undefinedundefined

    undefinedundefined! let func = new Function(‘alert(value)'''); undefinedundefined/!undefinedundefined

    undefinedundefined

    return func; }

    undefinedundefined

    getFunc()(); // error: value is not defined

    undefinedundefined

    Compare it with the regular behavior:

    undefinedundefined

    run function getFunc() { let value = "test";

    undefinedundefined

    undefinedundefined! let func = function() { alert(value); }; undefinedundefined/!undefinedundefined

    undefinedundefined

    return func; }

    undefinedundefined

    getFunc()(); // undefinedundefined!"test"undefinedundefined/!, from the Lexical Environment of getFunc undefinedundefined

    undefinedundefined

    This special feature of undefinedundefinednew Function looks strange, but appears very useful in practice.undefinedundefined

    undefinedundefined

    Imagine that we must create a function from a string. The code of that function is not known at the time of writing the script (that's why we don't use regular functions), but will be known in the process of execution. We may receive it from the server or from another source.

    undefinedundefined

    Our new function needs to interact with the main script.

    undefinedundefined

    What if it could access the outer variables?

    undefinedundefined

    The problem is that before JavaScript is published to production, it's compressed using a undefinedundefinedminifier - a special program that shrinks code by removing extra comments, spaces and - what's important, renames local variables into shorter ones.undefinedundefined

    undefinedundefined

    For instance, if a function has undefinedundefinedlet userName, minifier replaces it with undefinedundefinedlet a (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace.undefinedundefined

    undefinedundefined

    So if undefinedundefinednew Function had access to outer variables, it would be unable to find renamed undefinedundefineduserName.undefinedundefined

    undefinedundefined

    undefinedundefinedIf undefinedundefinednew Function had access to outer variables, it would have problems with minifiers.undefinedundefinedundefinedundefined

    undefinedundefined

    Besides, such code would be architecturally bad and prone to errors.

    undefinedundefined

    To pass something to a function, created as undefinedundefinednew Function, we should use its arguments.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    The syntax:

    undefinedundefinedundefinedundefined

    For historical reasons, arguments can also be given as a comma-separated list.

    undefinedundefined

    These three declarations mean the same:

    undefinedundefinedundefinedundefined

    Functions created with undefinedundefinednew Function, have undefinedundefined[[Environment]] referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that's actually good, because it insures us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers.undefinedundefined

    undefinedundefined

    Scheduling: setTimeout and setInterval

    undefinedundefined

    We may decide to execute a function not right now, but at a certain time later. That's called "scheduling a call".

    undefinedundefined

    There are two methods for it:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedsetTimeout allows us to run a function once after the interval of time.undefinedundefined
    • undefinedundefined
    • undefinedundefinedsetInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.undefinedundefined
    • undefinedundefined
    undefinedundefined

    These methods are not a part of JavaScript specification. But most environments have the internal scheduler and provide these methods. In particular, they are supported in all browsers and Node.js.

    undefinedundefined

    setTimeout

    undefinedundefined

    The syntax:

    undefinedundefinedundefinedundefined

    Parameters:

    undefinedundefined
    undefinedundefined
    undefinedundefinedfunc|codeundefinedundefined
    undefinedundefined
    Function or a string of code to execute. Usually, that's a function. For historical reasons, a string of code can be passed, but that's not recommended.
    undefinedundefined
    undefinedundefineddelayundefinedundefined
    undefinedundefined
    The delay before run, in milliseconds (1000 ms = 1 second), by default 0.
    undefinedundefined
    undefinedundefinedarg1, undefinedundefinedarg2…undefinedundefined
    undefinedundefined
    Arguments for the function (not supported in IE9-)
    undefinedundefined
    undefinedundefined

    For instance, this code calls undefinedundefinedsayHi() after one second:undefinedundefined

    undefinedundefined

    run function sayHi() { alert(‘Hello'); }

    undefinedundefined

    undefinedundefined! setTimeout(sayHi, 1000); undefinedundefined/! undefinedundefined

    undefinedundefined

    With arguments:

    undefinedundefined

    run function sayHi(phrase, who) { alert( phrase + ‘,''' + who ); }

    undefinedundefined

    undefinedundefined! setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John undefinedundefined/! undefinedundefined

    undefinedundefined

    If the first argument is a string, then JavaScript creates a function from it.

    undefinedundefined

    So, this will also work:

    undefinedundefined

    undefinedundefinedjs run no-beautify setTimeout("alert('Hello')", 1000);undefinedundefined

    undefinedundefined

    But using strings is not recommended, use arrow functions instead of them, like this:

    undefinedundefined

    undefinedundefinedjs run no-beautify setTimeout(() => alert('Hello'), 1000);undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Pass a function, but don't run it" Novice developers sometimes make a mistake by adding brackets()` after the function:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// wrong!undefinedundefinedundefinedundefinedundefinedundefinedsetTimeout(undefinedundefinedsayHi()undefinedundefined,undefinedundefined1000)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    That doesn't work, because undefinedundefinedsetTimeout expects a reference to a function. And here undefinedundefinedsayHi() runs the function, and the undefinedundefinedresult of its execution is passed to undefinedundefinedsetTimeout. In our case the result of undefinedundefinedsayHi() is undefinedundefinedundefined (the function returns nothing), so nothing is scheduled. undefinedundefined

    undefinedundefined

    Canceling with clearTimeout

    undefinedundefined

    A call to undefinedundefinedsetTimeout returns a "timer identifier" undefinedundefinedtimerId that we can use to cancel the execution.undefinedundefined

    undefinedundefined

    The syntax to cancel:

    undefinedundefinedundefinedundefined

    In the code below, we schedule the function and then cancel it (changed our mind). As a result, nothing happens:

    undefinedundefined

    run no-beautify let timerId = setTimeout(() => alert("never happens"), 1000); alert(timerId); // timer identifier

    undefinedundefined

    clearTimeout(timerId); alert(timerId); // same identifier (doesn't become null after canceling)

    undefinedundefined

    As we can see from undefinedundefinedalert output, in a browser the timer identifier is a number. In other environments, this can be something else. For instance, Node.js returns a timer object with additional methods.undefinedundefined

    undefinedundefined

    Again, there is no universal specification for these methods, so that's fine.

    undefinedundefined

    For browsers, timers are described in the undefinedundefinedtimers section of HTML5 standard.undefinedundefined

    undefinedundefined

    setInterval

    undefinedundefined

    The undefinedundefinedsetInterval method has the same syntax as undefinedundefinedsetTimeout:undefinedundefined

    undefinedundefinedundefinedundefined

    All arguments have the same meaning. But unlike undefinedundefinedsetTimeout it runs the function not only once, but regularly after the given interval of time.undefinedundefined

    undefinedundefined

    To stop further calls, we should call undefinedundefinedclearInterval(timerId).undefinedundefined

    undefinedundefined

    The following example will show the message every 2 seconds. After 5 seconds, the output is stopped:

    undefinedundefined

    run // repeat with the interval of 2 seconds let timerId = setInterval(() => alert(‘tick'), 2000);

    undefinedundefined

    // after 5 seconds stop setTimeout(() => { clearInterval(timerId); alert(‘stop'); }, 5000);

    undefinedundefined

    ``undefinedundefinedsmart header="Time goes on whilealertundefinedundefinedis shown" In most browsers, including Chrome and Firefox the internal timer continues "ticking" while showingalert/confirm/prompt`.undefinedundefined

    undefinedundefined

    So if you run the code above and don't dismiss the undefinedundefinedalert window for some time, then the next undefinedundefinedalert will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. undefinedundefined

    undefinedundefined

    Nested setTimeout

    undefinedundefined

    There are two ways of running something regularly.

    undefinedundefined

    One is undefinedundefinedsetInterval. The other one is a nested undefinedundefinedsetTimeout, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    The undefinedundefinedsetTimeout above schedules the next call right at the end of the current one undefinedundefined(*).undefinedundefined

    undefinedundefined

    The nested undefinedundefinedsetTimeout is a more flexible method than undefinedundefinedsetInterval. This way the next call may be scheduled differently, depending on the results of the current one.undefinedundefined

    undefinedundefined

    For instance, we need to write a service that sends a request to the server every 5 seconds asking for data, but in case the server is overloaded, it should increase the interval to 10, 20, 40 seconds…

    undefinedundefined

    Here's the pseudocode:

    undefinedundefinedundefinedundefined

    And if the functions that we're scheduling are CPU-hungry, then we can measure the time taken by the execution and plan the next call sooner or later.

    undefinedundefined

    undefinedundefinedNested undefinedundefinedsetTimeout allows to set the delay between the executions more precisely than undefinedundefinedsetInterval.undefinedundefinedundefinedundefined

    undefinedundefined

    Let's compare two code fragments. The first one uses undefinedundefinedsetInterval:undefinedundefined

    undefinedundefinedundefinedundefined

    The second one uses nested undefinedundefinedsetTimeout:undefinedundefined

    undefinedundefinedundefinedundefined

    For undefinedundefinedsetInterval the internal scheduler will run undefinedundefinedfunc(i++) every 100ms:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Did you notice?

    undefinedundefined

    undefinedundefinedThe real delay between undefinedundefinedfunc calls for undefinedundefinedsetInterval is less than in the code!undefinedundefinedundefinedundefined

    undefinedundefined

    That's normal, because the time taken by undefinedundefinedfunc's execution "consumes" a part of the interval.undefinedundefined

    undefinedundefined

    It is possible that undefinedundefinedfunc's execution turns out to be longer than we expected and takes more than 100ms.undefinedundefined

    undefinedundefined

    In this case the engine waits for undefinedundefinedfunc to complete, then checks the scheduler and if the time is up, runs it again undefinedundefinedimmediately.undefinedundefined

    undefinedundefined

    In the edge case, if the function always executes longer than undefinedundefineddelay ms, then the calls will happen without a pause at all.undefinedundefined

    undefinedundefined

    And here is the picture for the nested undefinedundefinedsetTimeout:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    undefinedundefinedThe nested undefinedundefinedsetTimeout guarantees the fixed delay (here 100ms).undefinedundefinedundefinedundefined

    undefinedundefined

    That's because a new call is planned at the end of the previous one.

    undefinedundefined

    undefinedundefinedsmart header="Garbage collection and setInterval/setTimeout callback" When a function is passed insetInterval/setTimeout`, an internal reference is created to it and saved in the scheduler. It prevents the function from being garbage collected, even if there are no other references to it.undefinedundefined

    undefinedundefinedundefinedundefined

    For undefinedundefinedsetInterval the function stays in memory until undefinedundefinedclearInterval is called.undefinedundefined

    undefinedundefined

    There's a side-effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small.

    undefinedundefined

    Zero delay setTimeout

    undefinedundefined

    There's a special use case: undefinedundefinedsetTimeout(func, 0), or just undefinedundefinedsetTimeout(func).undefinedundefined

    undefinedundefined

    This schedules the execution of undefinedundefinedfunc as soon as possible. But the scheduler will invoke it only after the currently executing script is complete.undefinedundefined

    undefinedundefined

    So the function is scheduled to run "right after" the current script.

    undefinedundefined

    For instance, this outputs "Hello", then immediately "World":

    undefinedundefined

    run setTimeout(() => alert("World"));

    undefinedundefined

    alert("Hello");

    undefinedundefined

    The first line "puts the call into calendar after 0ms". But the scheduler will only "check the calendar" after the current script is complete, so undefinedundefined"Hello" is first, and undefinedundefined"World" - after it.undefinedundefined

    undefinedundefined

    There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter undefinedundefinedinfo:event-loop.undefinedundefined

    undefinedundefined

    smart header="Zero delay is in fact not zero (in a browser)" In the browser, there's a limitation of how often nested timers can run. The undefinedundefinedHTML5 standard says: "after five nested timers, the interval is forced to be at least 4 milliseconds.".undefinedundefined

    undefinedundefined

    Let's demonstrate what it means with the example below. The undefinedundefinedsetTimeout call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the undefinedundefinedtimes array. What do the real delays look like? Let's see:undefinedundefined

    undefinedundefined

    run let start = Date.now(); let times = [];

    undefinedundefined

    setTimeout(function run() { times.push(Date.now() - start); // remember delay from the previous call

    undefinedundefined

    if (start + 100 < Date.now()) alert(times); // show the delays after 100ms else setTimeout(run); // else re-schedule });

    undefinedundefined

    // an example of the output: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100

    undefinedundefined
    undefinedundefined
    First timers run immediately (just as written in the spec), and then we see `9, 15, 20, 24...`. The 4+ ms obligatory delay between invocations comes into play.
    The similar thing happens if we use `setInterval` instead of `setTimeout`: `setInterval(f)` runs `f` few times with zero-delay, and afterwards with 4+ ms delay.
    That limitation comes from ancient times and many scripts rely on it, so it exists for historical reasons.
    For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific.undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Methods undefinedundefinedsetTimeout(func, delay, ...args) and undefinedundefinedsetInterval(func, delay, ...args) allow us to run the undefinedundefinedfunc once/regularly after undefinedundefineddelay milliseconds.undefinedundefined
    • undefinedundefined
    • To cancel the execution, we should call undefinedundefinedclearTimeout/clearInterval with the value returned by undefinedundefinedsetTimeout/setInterval.undefinedundefined
    • undefinedundefined
    • Nested undefinedundefinedsetTimeout calls are a more flexible alternative to undefinedundefinedsetInterval, allowing us to set the time undefinedundefinedbetween executions more precisely.undefinedundefined
    • undefinedundefined
    • Zero delay scheduling with undefinedundefinedsetTimeout(func, 0) (the same as undefinedundefinedsetTimeout(func)) is used to schedule the call "as soon as possible, but after the current script is complete".undefinedundefined
    • undefinedundefined
    • The browser limits the minimal delay for five or more nested calls of undefinedundefinedsetTimeout or for undefinedundefinedsetInterval (after 5th call) to 4ms. That's for historical reasons.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Please note that all scheduling methods do not undefinedundefinedguarantee the exact delay.undefinedundefined

    undefinedundefined

    For example, the in-browser timer may slow down for a lot of reasons: - The CPU is overloaded. - The browser tab is in the background mode. - The laptop is on battery.

    undefinedundefined

    All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings.

    undefinedundefined

    Decorators and forwarding, call/apply

    undefinedundefined

    JavaScript gives exceptional flexibility when dealing with functions. They can be passed around, used as objects, and now we'll see how to undefinedundefinedforward calls between them and undefinedundefineddecorate them.undefinedundefined

    undefinedundefined

    Transparent caching

    undefinedundefined

    Let's say we have a function undefinedundefinedslow(x) which is CPU-heavy, but its results are stable. In other words, for the same undefinedundefinedx it always returns the same result.undefinedundefined

    undefinedundefined

    If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations.

    undefinedundefined

    But instead of adding that functionality into undefinedundefinedslow() we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so.undefinedundefined

    undefinedundefined

    Here's the code, and explanations follow:

    undefinedundefined

    ``undefinedundefinedjs run function slow(x) { // there can be a heavy CPU-intensive job here alert(Called with ${x}`); return x; }undefinedundefined

    undefinedundefined

    function cachingDecorator(func) { let cache = new Map();

    undefinedundefined

    return function(x) { if (cache.has(x)) { // if there's such key in cache return cache.get(x); // read the result from it }

    undefinedundefined
    undefinedundefinedlet result = func(x);  // otherwise call func
    cache.set(x, result);  // and cache (remember) the result
    return result;undefinedundefined
    undefinedundefined

    }; }

    undefinedundefined

    slow = cachingDecorator(slow);

    undefinedundefined

    alert( slow(1) ); // slow(1) is cached and the result returned alert( "Again:" + slow(1) ); // slow(1) result returned from cache

    undefinedundefined

    alert( slow(2) ); // slow(2) is cached and the result returned alert( "Again:" + slow(2) ); // slow(2) result returned from cache

    undefinedundefined

    In the code above undefinedundefinedcachingDecorator is a undefinedundefineddecorator: a special function that takes another function and alters its behavior.undefinedundefined

    undefinedundefined

    The idea is that we can call undefinedundefinedcachingDecorator for any function, and it will return the caching wrapper. That's great, because we can have many functions that could use such a feature, and all we need to do is to apply undefinedundefinedcachingDecorator to them.undefinedundefined

    undefinedundefined

    By separating caching from the main function code we also keep the main code simpler.

    undefinedundefined

    The result of undefinedundefinedcachingDecorator(func) is a "wrapper": undefinedundefinedfunction(x) that "wraps" the call of undefinedundefinedfunc(x) into caching logic:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    From an outside code, the wrapped undefinedundefinedslow function still does the same. It just got a caching aspect added to its behavior.undefinedundefined

    undefinedundefined

    To summarize, there are several benefits of using a separate undefinedundefinedcachingDecorator instead of altering the code of undefinedundefinedslow itself:undefinedundefined

    undefinedundefined
      undefinedundefined
    • The undefinedundefinedcachingDecorator is reusable. We can apply it to another function.undefinedundefined
    • undefinedundefined
    • The caching logic is separate, it did not increase the complexity of undefinedundefinedslow itself (if there was any).undefinedundefined
    • undefinedundefined
    • We can combine multiple decorators if needed (other decorators will follow).
    • undefinedundefined
    undefinedundefined

    Using "func.call" for the context

    undefinedundefined

    The caching decorator mentioned above is not suited to work with object methods.

    undefinedundefined

    For instance, in the code below undefinedundefinedworker.slow() stops working after the decoration:undefinedundefined

    undefinedundefined

    run // we'll make worker.slow caching let worker = { someMethod() { return 1; },

    undefinedundefined

    slow(x) { // scary CPU-heavy task hereundefinedundefined
    alert("Called with" + x); return x * this.someMethod(); // (*) } };undefinedundefined

    undefinedundefined

    // same code as before function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } undefinedundefined! let result = func(x); // (**) undefinedundefined/! cache.set(x, result); return result; }; }undefinedundefined

    undefinedundefined

    alert( worker.slow(1) ); // the original method works

    undefinedundefined

    worker.slow = cachingDecorator(worker.slow); // now make it caching

    undefinedundefined

    undefinedundefined! alert( worker.slow(2) ); // Whoops! Error: Cannot read property ‘someMethod' of undefined undefinedundefined/! undefinedundefined

    undefinedundefined

    The error occurs in the line undefinedundefined(*) that tries to access undefinedundefinedthis.someMethod and fails. Can you see why?undefinedundefined

    undefinedundefined

    The reason is that the wrapper calls the original function as undefinedundefinedfunc(x) in the line undefinedundefined(**). And, when called like that, the function gets undefinedundefinedthis = undefined.undefinedundefined

    undefinedundefined

    We would observe a similar symptom if we tried to run:

    undefinedundefinedundefinedundefined

    So, the wrapper passes the call to the original method, but without the context undefinedundefinedthis. Hence the error.undefinedundefined

    undefinedundefined

    Let's fix it.

    undefinedundefined

    There's a special built-in function method undefinedundefinedfunc.call(context, …args) that allows to call a function explicitly setting undefinedundefinedthis.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunc.undefinedundefinedcall(contextundefinedundefined, arg1undefinedundefined, arg2undefinedundefined, ...)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It runs undefinedundefinedfunc providing the first argument as undefinedundefinedthis, and the next as the arguments.undefinedundefined

    undefinedundefined

    To put it simply, these two calls do almost the same:

    undefinedundefinedundefinedundefined

    They both call undefinedundefinedfunc with arguments undefinedundefined1, undefinedundefined2 and undefinedundefined3. The only difference is that undefinedundefinedfunc.call also sets undefinedundefinedthis to undefinedundefinedobj.undefinedundefined

    undefinedundefined

    As an example, in the code below we call undefinedundefinedsayHi in the context of different objects: undefinedundefinedsayHi.call(user) runs undefinedundefinedsayHi providing undefinedundefinedthis=user, and the next line sets undefinedundefinedthis=admin:undefinedundefined

    undefinedundefined

    run function sayHi() { alert(this.name); }

    undefinedundefined

    let user = { name: "John" }; let admin = { name: "Admin" };

    undefinedundefined

    // use call to pass different objects as "this" sayHi.call( user ); // John sayHi.call( admin ); // Admin

    undefinedundefined

    And here we use undefinedundefinedcall to call undefinedundefinedsay with the given context and phrase:undefinedundefined

    undefinedundefined

    run function say(phrase) { alert(this.name + ‘:''' + phrase); }

    undefinedundefined

    let user = { name: "John" };

    undefinedundefined

    // user becomes this, and "Hello" becomes the first argument say.call( user, "Hello" ); // John: Hello

    undefinedundefined

    In our case, we can use undefinedundefinedcall in the wrapper to pass the context to the original function:undefinedundefined

    undefinedundefined

    run let worker = { someMethod() { return 1; },

    undefinedundefined

    slow(x) { alert("Called with" + x); return x * this.someMethod(); // (*) } };

    undefinedundefined

    function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } undefinedundefined! let result = func.call(this, x); // "this" is passed correctly now undefinedundefined/! cache.set(x, result); return result; }; }undefinedundefined

    undefinedundefined

    worker.slow = cachingDecorator(worker.slow); // now make it caching

    undefinedundefined

    alert( worker.slow(2) ); // works alert( worker.slow(2) ); // works, doesn't call the original (cached)

    undefinedundefined

    Now everything is fine.

    undefinedundefined

    To make it all clear, let's see more deeply how undefinedundefinedthis is passed along:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. After the decoration undefinedundefinedworker.slow is now the wrapper undefinedundefinedfunction (x) { ... }.undefinedundefined
    2. undefinedundefined
    3. So when undefinedundefinedworker.slow(2) is executed, the wrapper gets undefinedundefined2 as an argument and undefinedundefinedthis=worker (it's the object before dot).undefinedundefined
    4. undefinedundefined
    5. Inside the wrapper, assuming the result is not yet cached, undefinedundefinedfunc.call(this, x) passes the current undefinedundefinedthis (undefinedundefined=worker) and the current argument (undefinedundefined=2) to the original method.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Going multi-argument

    undefinedundefined

    Now let's make undefinedundefinedcachingDecorator even more universal. Till now it was working only with single-argument functions.undefinedundefined

    undefinedundefined

    Now how to cache the multi-argument undefinedundefinedworker.slow method?undefinedundefined

    undefinedundefinedundefinedundefined

    Previously, for a single argument undefinedundefinedx we could just undefinedundefinedcache.set(x, result) to save the result and undefinedundefinedcache.get(x) to retrieve it. But now we need to remember the result for a undefinedundefinedcombination of argumentsundefinedundefined(min,max). The native undefinedundefinedMap takes single value only as the key.undefinedundefined

    undefinedundefined

    There are many solutions possible:

    undefinedundefined
      undefinedundefined
    1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys.
    2. undefinedundefined
    3. Use nested maps: undefinedundefinedcache.set(min) will be a undefinedundefinedMap that stores the pair undefinedundefined(max, result). So we can get undefinedundefinedresult as undefinedundefinedcache.get(min).get(max).undefinedundefined
    4. undefinedundefined
    5. Join two values into one. In our particular case we can just use a string undefinedundefined"min,max" as the undefinedundefinedMap key. For flexibility, we can allow to provide a undefinedundefinedhashing function for the decorator, that knows how to make one value from many.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    For many practical applications, the 3rd variant is good enough, so we'll stick to it.

    undefinedundefined

    Also we need to pass not just undefinedundefinedx, but all arguments in undefinedundefinedfunc.call. Let's recall that in a undefinedundefinedfunction() we can get a pseudo-array of its arguments as undefinedundefinedarguments, so undefinedundefinedfunc.call(this, x) should be replaced with undefinedundefinedfunc.call(this, ...arguments).undefinedundefined

    undefinedundefined

    Here's a more powerful undefinedundefinedcachingDecorator:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let worker = { slow(min, max) { alert(Called with undefinedundefinedundefinedundefinedmundefinedundefinediundefinedundefinedn,undefinedundefined{max}`); return min + max; } };undefinedundefined

    undefinedundefined

    function cachingDecorator(func, hash) { let cache = new Map(); return function() { undefinedundefined! let key = hash(arguments); // (undefinedundefined) /!* if (cache.has(key)) { return cache.get(key); }undefinedundefined

    undefinedundefined

    undefinedundefined! let result = func.call(this, …arguments); // (**) undefinedundefined/!undefinedundefined

    undefinedundefined
    undefinedundefinedcache.set(key, result);
    return result;undefinedundefined
    undefinedundefined

    }; }

    undefinedundefined

    function hash(args) { return args[0] + ‘,''' + args[1]; }

    undefinedundefined

    worker.slow = cachingDecorator(worker.slow, hash);

    undefinedundefined

    alert( worker.slow(3, 5) ); // works alert( "Again" + worker.slow(3, 5) ); // same (cached)

    undefinedundefined

    Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below).

    undefinedundefined

    There are two changes:

    undefinedundefined
      undefinedundefined
    • In the line undefinedundefined(*) it calls undefinedundefinedhash to create a single key from undefinedundefinedarguments. Here we use a simple "joining" function that turns arguments undefinedundefined(3, 5) into the key undefinedundefined"3,5". More complex cases may require other hashing functions.undefinedundefined
    • undefinedundefined
    • Then undefinedundefined(**) uses undefinedundefinedfunc.call(this, ...arguments) to pass both the context and all arguments the wrapper got (not just the first one) to the original function.undefinedundefined
    • undefinedundefined
    undefinedundefined

    func.apply

    undefinedundefined

    Instead of undefinedundefinedfunc.call(this, ...arguments) we could use undefinedundefinedfunc.apply(this, arguments).undefinedundefined

    undefinedundefined

    The syntax of built-in method undefinedundefinedfunc.apply is:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunc.undefinedundefinedapply(contextundefinedundefined, args)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It runs the undefinedundefinedfunc setting undefinedundefinedthis=context and using an array-like object undefinedundefinedargs as the list of arguments.undefinedundefined

    undefinedundefined

    The only syntax difference between undefinedundefinedcall and undefinedundefinedapply is that undefinedundefinedcall expects a list of arguments, while undefinedundefinedapply takes an array-like object with them.undefinedundefined

    undefinedundefined

    So these two calls are almost equivalent:

    undefinedundefinedundefinedundefined

    There's only a subtle difference:

    undefinedundefined
      undefinedundefined
    • The spread syntax undefinedundefined... allows to pass undefinedundefinediterableundefinedundefinedargs as the list to undefinedundefinedcall.undefinedundefined
    • undefinedundefined
    • The undefinedundefinedapply accepts only undefinedundefinedarray-likeundefinedundefinedargs.undefinedundefined
    • undefinedundefined
    undefinedundefined

    So, where we expect an iterable, undefinedundefinedcall works, and where we expect an array-like, undefinedundefinedapply works.undefinedundefined

    undefinedundefined

    And for objects that are both iterable and array-like, like a real array, we can use any of them, but undefinedundefinedapply will probably be faster, because most JavaScript engines internally optimize it better.undefinedundefined

    undefinedundefined

    Passing all arguments along with the context to another function is called undefinedundefinedcall forwarding.undefinedundefined

    undefinedundefined

    That's the simplest form of it:

    undefinedundefinedundefinedundefined

    When an external code calls such undefinedundefinedwrapper, it is indistinguishable from the call of the original function undefinedundefinedfunc.undefinedundefined

    undefinedundefined

    Borrowing a method [#method-borrowing]

    undefinedundefined

    Now let's make one more minor improvement in the hashing function:

    undefinedundefinedundefinedundefined

    As of now, it works only on two arguments. It would be better if it could glue any number of undefinedundefinedargs.undefinedundefined

    undefinedundefined

    The natural solution would be to use undefinedundefinedarr.join method:undefinedundefined

    undefinedundefinedundefinedundefined

    …Unfortunately, that won't work. Because we are calling undefinedundefinedhash(arguments), and undefinedundefinedarguments object is both iterable and array-like, but not a real array.undefinedundefined

    undefinedundefined

    So calling undefinedundefinedjoin on it would fail, as we can see below:undefinedundefined

    undefinedundefined

    run function hash() { undefinedundefined! alert( arguments.join() ); // Error: arguments.join is not a function undefinedundefined/! }undefinedundefined

    undefinedundefined

    hash(1, 2);

    undefinedundefined

    Still, there's an easy way to use array join:

    undefinedundefined

    run function hash() { undefinedundefined! alert( [].join.call(arguments) ); // 1,2 undefinedundefined/! }undefinedundefined

    undefinedundefined

    hash(1, 2);

    undefinedundefined

    The trick is called undefinedundefinedmethod borrowing.undefinedundefined

    undefinedundefined

    We take (borrow) a join method from a regular array (undefinedundefined[].join) and use undefinedundefined[].join.call to run it in the context of undefinedundefinedarguments.undefinedundefined

    undefinedundefined

    Why does it work?

    undefinedundefined

    That's because the internal algorithm of the native method undefinedundefinedarr.join(glue) is very simple.undefinedundefined

    undefinedundefined

    Taken from the specification almost "as-is":

    undefinedundefined
      undefinedundefined
    1. Let undefinedundefinedglue be the first argument or, if no arguments, then a comma undefinedundefined",".undefinedundefined
    2. undefinedundefined
    3. Let undefinedundefinedresult be an empty string.undefinedundefined
    4. undefinedundefined
    5. Append undefinedundefinedthis[0] to undefinedundefinedresult.undefinedundefined
    6. undefinedundefined
    7. Append undefinedundefinedglue and undefinedundefinedthis[1].undefinedundefined
    8. undefinedundefined
    9. Append undefinedundefinedglue and undefinedundefinedthis[2].undefinedundefined
    10. undefinedundefined
    11. …Do so until undefinedundefinedthis.length items are glued.undefinedundefined
    12. undefinedundefined
    13. Return undefinedundefinedresult.undefinedundefined
    14. undefinedundefined
    undefinedundefined

    So, technically it takes undefinedundefinedthis and joins undefinedundefinedthis[0], undefinedundefinedthis[1] …etc together. It's intentionally written in a way that allows any array-like undefinedundefinedthis (not a coincidence, many methods follow this practice). That's why it also works with undefinedundefinedthis=arguments.undefinedundefined

    undefinedundefined

    Decorators and function properties

    undefinedundefined

    It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like undefinedundefinedfunc.calledCount or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them.undefinedundefined

    undefinedundefined

    E.g. in the example above if undefinedundefinedslow function had any properties on it, then undefinedundefinedcachingDecorator(slow) is a wrapper without them.undefinedundefined

    undefinedundefined

    Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties.

    undefinedundefined

    There exists a way to create decorators that keep access to function properties, but this requires using a special undefinedundefinedProxy object to wrap a function. We'll discuss it later in the article undefinedundefinedinfo:proxy#proxy-apply.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedDecorator is a wrapper around a function that alters its behavior. The main job is still carried out by the function.undefinedundefined

    undefinedundefined

    Decorators can be seen as "features" or "aspects" that can be added to a function. We can add one or add many. And all this without changing its code!

    undefinedundefined

    To implement undefinedundefinedcachingDecorator, we studied methods:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedfunc.call(context, arg1, arg2…) - calls undefinedundefinedfunc with given context and arguments.undefinedundefined
    • undefinedundefined
    • undefinedundefinedfunc.apply(context, args) - calls undefinedundefinedfunc passing undefinedundefinedcontext as undefinedundefinedthis and array-like undefinedundefinedargs into a list of arguments.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The generic undefinedundefinedcall forwarding is usually done with undefinedundefinedapply:undefinedundefined

    undefinedundefinedundefinedundefined

    We also saw an example of undefinedundefinedmethod borrowing when we take a method from an object and undefinedundefinedcall it in the context of another object. It is quite common to take array methods and apply them to undefinedundefinedarguments. The alternative is to use rest parameters object that is a real array.undefinedundefined

    undefinedundefined

    There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter.

    undefinedundefined

    libs: - lodash

    undefinedundefined
    undefinedundefined

    Function binding

    undefinedundefined

    When passing object methods as callbacks, for instance to undefinedundefinedsetTimeout, there's a known problem: "losing undefinedundefinedthis".undefinedundefined

    undefinedundefined

    In this chapter we'll see the ways to fix it.

    undefinedundefined

    Losing "this"

    undefinedundefined

    We've already seen examples of losing undefinedundefinedthis. Once a method is passed somewhere separately from the object - undefinedundefinedthis is lost.undefinedundefined

    undefinedundefined

    Here's how it may happen with undefinedundefinedsetTimeout:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let user = { firstName: "John", sayHi() { alert(Hello, ${this.firstName}!`); } };undefinedundefined

    undefinedundefined

    undefinedundefined! setTimeout(user.sayHi, 1000); // Hello, undefined! undefinedundefined/! undefinedundefined

    undefinedundefined

    As we can see, the output shows not "John" as undefinedundefinedthis.firstName, but undefinedundefinedundefined!undefinedundefined

    undefinedundefined

    That's because undefinedundefinedsetTimeout got the function undefinedundefineduser.sayHi, separately from the object. The last line can be rewritten as:undefinedundefined

    undefinedundefinedundefinedundefined

    The method undefinedundefinedsetTimeout in-browser is a little special: it sets undefinedundefinedthis=window for the function call (for Node.js, undefinedundefinedthis becomes the timer object, but doesn't really matter here). So for undefinedundefinedthis.firstName it tries to get undefinedundefinedwindow.firstName, which does not exist. In other similar cases, usually undefinedundefinedthis just becomes undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    The task is quite typical - we want to pass an object method somewhere else (here - to the scheduler) where it will be called. How to make sure that it will be called in the right context?

    undefinedundefined

    Solution 1: a wrapper

    undefinedundefined

    The simplest solution is to use a wrapping function:

    undefinedundefined

    ``undefinedundefinedjs run let user = { firstName: "John", sayHi() { alert(Hello, ${this.firstName}!`); } };undefinedundefined

    undefinedundefined

    undefinedundefined! setTimeout(function() { user.sayHi(); // Hello, John! }, 1000); undefinedundefined/! undefinedundefined

    undefinedundefined

    Now it works, because it receives undefinedundefineduser from the outer lexical environment, and then calls the method normally.undefinedundefined

    undefinedundefined

    The same, but shorter:

    undefinedundefinedundefinedundefined

    Looks fine, but a slight vulnerability appears in our code structure.

    undefinedundefined

    What if before undefinedundefinedsetTimeout triggers (there's one second delay!) undefinedundefineduser changes value? Then, suddenly, it will call the wrong object!undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let user = { firstName: "John", sayHi() { alert(Hello, ${this.firstName}!`); } };undefinedundefined

    undefinedundefined

    setTimeout(() => user.sayHi(), 1000);

    undefinedundefined

    // …the value of user changes within 1 second user = { sayHi() { alert("Another user in setTimeout!"); } };

    undefinedundefined

    // Another user in setTimeout!

    undefinedundefined

    The next solution guarantees that such thing won't happen.

    undefinedundefined

    Solution 2: bind

    undefinedundefined

    Functions provide a built-in method undefinedundefinedbind that allows to fix undefinedundefinedthis.undefinedundefined

    undefinedundefined

    The basic syntax is:

    undefinedundefinedundefinedundefined

    The result of undefinedundefinedfunc.bind(context) is a special function-like "exotic object", that is callable as function and transparently passes the call to undefinedundefinedfunc setting undefinedundefinedthis=context.undefinedundefined

    undefinedundefined

    In other words, calling undefinedundefinedboundFunc is like undefinedundefinedfunc with fixed undefinedundefinedthis.undefinedundefined

    undefinedundefined

    For instance, here undefinedundefinedfuncUser passes a call to undefinedundefinedfunc with undefinedundefinedthis=user:undefinedundefined

    undefinedundefined

    runundefinedundefined
    let user = { firstName: "John" };undefinedundefined

    undefinedundefined

    function func() { alert(this.firstName); }

    undefinedundefined

    undefinedundefined! let funcUser = func.bind(user); funcUser(); // Johnundefinedundefined
    undefinedundefined/! undefinedundefined

    undefinedundefined

    Here undefinedundefinedfunc.bind(user) as a "bound variant" of undefinedundefinedfunc, with fixed undefinedundefinedthis=user.undefinedundefined

    undefinedundefined

    All arguments are passed to the original undefinedundefinedfunc "as is", for instance:undefinedundefined

    undefinedundefined

    runundefinedundefined
    let user = { firstName: "John" };undefinedundefined

    undefinedundefined

    function func(phrase) { alert(phrase + ‘,''' + this.firstName); }

    undefinedundefined

    // bind this to user let funcUser = func.bind(user);

    undefinedundefined

    undefinedundefined! funcUser("Hello"); // Hello, John (argument "Hello" is passed, and this=user) undefinedundefined/! undefinedundefined

    undefinedundefined

    Now let's try with an object method:

    undefinedundefined

    ``undefinedundefinedjs run let user = { firstName: "John", sayHi() { alert(Hello, ${this.firstName}!`); } };undefinedundefined

    undefinedundefined

    undefinedundefined! let sayHi = user.sayHi.bind(user); // (undefinedundefined) /!*undefinedundefined

    undefinedundefined

    // can run it without an object sayHi(); // Hello, John!

    undefinedundefined

    setTimeout(sayHi, 1000); // Hello, John!

    undefinedundefined

    // even if the value of user changes within 1 second // sayHi uses the pre-bound value which is reference to the old user object user = { sayHi() { alert("Another user in setTimeout!"); } };

    undefinedundefined

    In the line undefinedundefined(*) we take the method undefinedundefineduser.sayHi and bind it to undefinedundefineduser. The undefinedundefinedsayHi is a "bound" function, that can be called alone or passed to undefinedundefinedsetTimeout - doesn't matter, the context will be right.undefinedundefined

    undefinedundefined

    Here we can see that arguments are passed "as is", only undefinedundefinedthis is fixed by undefinedundefinedbind:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let user = { firstName: "John", say(phrase) { alert(${phrase}, ${this.firstName}!`); } };undefinedundefined

    undefinedundefined

    let say = user.say.bind(user);

    undefinedundefined

    say("Hello"); // Hello, John ("Hello" argument is passed to say) say("Bye"); // Bye, John ("Bye" is passed to say)

    undefinedundefined

    undefinedundefinedsmart header="Convenience method:bindAll`" If an object has many methods and we plan to actively pass it around, then we could bind them all in a loop:undefinedundefined

    undefinedundefinedundefinedundefined

    JavaScript libraries also provide functions for convenient mass binding , e.g. undefinedundefined_.bindAll(object, methodNames) in lodash. undefinedundefined

    undefinedundefined

    Partial functions

    undefinedundefined

    Until now we have only been talking about binding undefinedundefinedthis. Let's take it a step further.undefinedundefined

    undefinedundefined

    We can bind not only undefinedundefinedthis, but also arguments. That's rarely done, but sometimes can be handy.undefinedundefined

    undefinedundefined

    The full syntax of undefinedundefinedbind:undefinedundefined

    undefinedundefinedundefinedundefined

    It allows to bind context as undefinedundefinedthis and starting arguments of the function.undefinedundefined

    undefinedundefined

    For instance, we have a multiplication function undefinedundefinedmul(a, b):undefinedundefined

    undefinedundefinedundefinedundefined

    Let's use undefinedundefinedbind to create a function undefinedundefineddouble on its base:undefinedundefined

    undefinedundefined

    run function mul(a, b) { return a * b; }

    undefinedundefined

    undefinedundefined! let double = mul.bind(null, 2); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10

    undefinedundefined

    The call to undefinedundefinedmul.bind(null, 2) creates a new function undefinedundefineddouble that passes calls to undefinedundefinedmul, fixing undefinedundefinednull as the context and undefinedundefined2 as the first argument. Further arguments are passed "as is".undefinedundefined

    undefinedundefined

    That's called undefinedundefinedpartial function application - we create a new function by fixing some parameters of the existing one.undefinedundefined

    undefinedundefined

    Please note that we actually don't use undefinedundefinedthis here. But undefinedundefinedbind requires it, so we must put in something like undefinedundefinednull.undefinedundefined

    undefinedundefined

    The function undefinedundefinedtriple in the code below triples the value:undefinedundefined

    undefinedundefined

    run function mul(a, b) { return a * b; }

    undefinedundefined

    undefinedundefined! let triple = mul.bind(null, 3); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15

    undefinedundefined

    Why do we usually make a partial function?

    undefinedundefined

    The benefit is that we can create an independent function with a readable name (undefinedundefineddouble, undefinedundefinedtriple). We can use it and not provide the first argument every time as it's fixed with undefinedundefinedbind.undefinedundefined

    undefinedundefined

    In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience.

    undefinedundefined

    For instance, we have a function undefinedundefinedsend(from, to, text). Then, inside a undefinedundefineduser object we may want to use a partial variant of it: undefinedundefinedsendTo(to, text) that sends from the current user.undefinedundefined

    undefinedundefined

    Going partial without context

    undefinedundefined

    What if we'd like to fix some arguments, but not the context undefinedundefinedthis? For example, for an object method.undefinedundefined

    undefinedundefined

    The native undefinedundefinedbind does not allow that. We can't just omit the context and jump to arguments.undefinedundefined

    undefinedundefined

    Fortunately, a function undefinedundefinedpartial for binding only arguments can be easily implemented.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run undefinedundefined! function partial(func, …argsBound) { return function(…args) { // (undefinedundefined) return func.call(this, …argsBound, …args); } } /!*undefinedundefined

    undefinedundefined

    // Usage: let user = { firstName: "John", say(time, phrase) { alert(undefinedundefined[${time}] ${this.firstName}: ${phrase}!); } };undefinedundefined

    undefinedundefined

    // add a partial method with fixed time user.sayNow = partial(user.say, new Date().getHours() + ‘:''' + new Date().getMinutes());

    undefinedundefined

    user.sayNow("Hello"); // Something like: // [10:00] John: Hello!

    undefinedundefined

    The result of undefinedundefinedpartial(func[, arg1, arg2...]) call is a wrapper undefinedundefined(*) that calls undefinedundefinedfunc with: - Same undefinedundefinedthis as it gets (for undefinedundefineduser.sayNow call it's undefinedundefineduser) - Then gives it undefinedundefined...argsBound - arguments from the undefinedundefinedpartial call (undefinedundefined"10:00") - Then gives it undefinedundefined...args - arguments given to the wrapper (undefinedundefined"Hello")undefinedundefined

    undefinedundefined

    So easy to do it with the spread syntax, right?

    undefinedundefined

    Also there's a ready undefinedundefined_.partial implementation from lodash library.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Method undefinedundefinedfunc.bind(context, ...args) returns a "bound variant" of function undefinedundefinedfunc that fixes the context undefinedundefinedthis and first arguments if given.undefinedundefined

    undefinedundefined

    Usually we apply undefinedundefinedbind to fix undefinedundefinedthis for an object method, so that we can pass it somewhere. For example, to undefinedundefinedsetTimeout.undefinedundefined

    undefinedundefined

    When we fix some arguments of an existing function, the resulting (less universal) function is called undefinedundefinedpartially applied or undefinedundefinedpartial.undefinedundefined

    undefinedundefined

    Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a undefinedundefinedsend(from, to) function, and undefinedundefinedfrom should always be the same for our task, we can get a partial and go on with it.undefinedundefined

    undefinedundefined

    Arrow functions revisited

    undefinedundefined

    Let's revisit arrow functions.

    undefinedundefined

    Arrow functions are not just a "shorthand" for writing small stuff. They have some very specific and useful features.

    undefinedundefined

    JavaScript is full of situations where we need to write a small function that's executed somewhere else.

    undefinedundefined

    For instance:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedarr.forEach(func) - undefinedundefinedfunc is executed by undefinedundefinedforEach for every array item.undefinedundefined
    • undefinedundefined
    • undefinedundefinedsetTimeout(func) - undefinedundefinedfunc is executed by the built-in scheduler.undefinedundefined
    • undefinedundefined
    • …there are more.
    • undefinedundefined
    undefinedundefined

    It's in the very spirit of JavaScript to create a function and pass it somewhere.

    undefinedundefined

    And in such functions we usually don't want to leave the current context. That's where arrow functions come in handy.

    undefinedundefined

    Arrow functions have no "this"

    undefinedundefined

    As we remember from the chapter undefinedundefinedinfo:object-methods, arrow functions do not have undefinedundefinedthis. If undefinedundefinedthis is accessed, it is taken from the outside.undefinedundefined

    undefinedundefined

    For instance, we can use it to iterate inside an object method:

    undefinedundefined

    run let group = { title: "Our Group", students: ["John", "Pete", "Alice"],

    undefinedundefined

    showList() { undefinedundefined! this.students.forEach( student => alert(this.title + ‘:''' + student) ); undefinedundefined/! } };undefinedundefined

    undefinedundefined

    group.showList();

    undefinedundefined

    Here in undefinedundefinedforEach, the arrow function is used, so undefinedundefinedthis.title in it is exactly the same as in the outer method undefinedundefinedshowList. That is: undefinedundefinedgroup.title.undefinedundefined

    undefinedundefined

    If we used a "regular" function, there would be an error:

    undefinedundefined

    run let group = { title: "Our Group", students: ["John", "Pete", "Alice"],

    undefinedundefined

    showList() { undefinedundefined! this.students.forEach(function(student) { // Error: Cannot read property ‘title' of undefined alert(this.title + ‘:''' + student); }); undefinedundefined/! } };undefinedundefined

    undefinedundefined

    group.showList();

    undefinedundefined

    The error occurs because undefinedundefinedforEach runs functions with undefinedundefinedthis=undefined by default, so the attempt to access undefinedundefinedundefined.title is made.undefinedundefined

    undefinedundefined

    That doesn't affect arrow functions, because they just don't have undefinedundefinedthis.undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Arrow functions can't run with `new`" Not having `this` naturally means another limitation: arrow functions can't be used as constructors. They can't be called with `new`.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Arrow functions VS bind" There's a subtle difference between an arrow function=>undefinedundefinedand a regular function called with.bind(this)`:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefined.bind(this) creates a "bound version" of the function.undefinedundefined
    • undefinedundefined
    • The arrow undefinedundefined=> doesn't create any binding. The function simply doesn't have undefinedundefinedthis. The lookup of undefinedundefinedthis is made exactly the same way as a regular variable search: in the outer lexical environment. undefinedundefined
    • undefinedundefined
    undefinedundefined

    Arrows have no "arguments"

    undefinedundefined

    Arrow functions also have no undefinedundefinedarguments variable.undefinedundefined

    undefinedundefined

    That's great for decorators, when we need to forward a call with the current undefinedundefinedthis and undefinedundefinedarguments.undefinedundefined

    undefinedundefined

    For instance, undefinedundefineddefer(f, ms) gets a function and returns a wrapper around it that delays the call by undefinedundefinedms milliseconds:undefinedundefined

    undefinedundefined

    run function defer(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; }

    undefinedundefined

    function sayHi(who) { alert(‘Hello,''' + who); }

    undefinedundefined

    let sayHiDeferred = defer(sayHi, 2000); sayHiDeferred("John"); // Hello, John after 2 seconds

    undefinedundefined

    The same without an arrow function would look like:

    undefinedundefinedundefinedundefined

    Here we had to create additional variables undefinedundefinedargs and undefinedundefinedctx so that the function inside undefinedundefinedsetTimeout could take them.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Arrow functions:

    undefinedundefined
      undefinedundefined
    • Do not have undefinedundefinedthisundefinedundefined
    • undefinedundefined
    • Do not have undefinedundefinedargumentsundefinedundefined
    • undefinedundefined
    • Can't be called with undefinedundefinednewundefinedundefined
    • undefinedundefined
    • They also don't have undefinedundefinedsuper, but we didn't study it yet. We will on the chapter undefinedundefinedinfo:class-inheritanceundefinedundefined
    • undefinedundefined
    undefinedundefined

    That's because they are meant for short pieces of code that do not have their own "context", but rather work in the current one. And they really shine in that use case.

    undefinedundefined

    Code structure

    undefinedundefined

    The first thing we'll study is the building blocks of code.

    undefinedundefined

    Statements

    undefinedundefined

    Statements are syntax constructs and commands that perform actions.

    undefinedundefined

    We've already seen a statement, undefinedundefinedalert('Hello, world!'), which shows the message "Hello, world!".undefinedundefined

    undefinedundefined

    We can have as many statements in our code as we want. Statements can be separated with a semicolon.

    undefinedundefined

    For example, here we split "Hello World" into two alerts:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert('Hello'); alert('World');undefinedundefined

    undefinedundefined

    Usually, statements are written on separate lines to make the code more readable:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert('Hello'); alert('World');undefinedundefined

    undefinedundefined

    Semicolons [#semicolon]

    undefinedundefined

    A semicolon may be omitted in most cases when a line break exists.

    undefinedundefined

    This would also work:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert('Hello') alert('World')undefinedundefined

    undefinedundefined

    Here, JavaScript interprets the line break as an "implicit" semicolon. This is called an undefinedundefinedautomatic semicolon insertion.undefinedundefined

    undefinedundefined

    undefinedundefinedIn most cases, a newline implies a semicolon. But "in most cases" does not mean "always"!undefinedundefined

    undefinedundefined

    There are cases when a newline does not mean a semicolon. For example:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert(3 + 1 + 2);undefinedundefined

    undefinedundefined

    The code outputs undefinedundefined6 because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus undefinedundefined"+", then it is an "incomplete expression", so the semicolon is not required. And in this case that works as intended.undefinedundefined

    undefinedundefined

    undefinedundefinedBut there are situations where JavaScript "fails" to assume a semicolon where it is really needed.undefinedundefined

    undefinedundefined

    Errors which occur in such cases are quite hard to find and fix.

    undefinedundefined

    smart header="An example of an error" If you're curious to see a concrete example of such an error, check this code out:

    undefinedundefined

    undefinedundefinedjs run [1, 2].forEach(alert)undefinedundefined

    undefinedundefined

    No need to think about the meaning of the brackets undefinedundefined[] and undefinedundefinedforEach yet. We'll study them later. For now, just remember the result of the code: it shows undefinedundefined1 then undefinedundefined2.undefinedundefined

    undefinedundefined

    Now, let's add an undefinedundefinedalert before the code and undefinedundefinednot finish it with a semicolon:undefinedundefined

    undefinedundefined

    run no-beautify alert("There will be an error")

    undefinedundefined

    [1, 2].forEach(alert)

    undefinedundefined

    Now if we run the code, only the first undefinedundefinedalert is shown and then we have an error!undefinedundefined

    undefinedundefined

    But everything is fine again if we add a semicolon after undefinedundefinedalert: run alert("All fine now");undefinedundefined

    undefinedundefined

    [1, 2].forEach(alert)undefinedundefined
    undefinedundefined

    undefinedundefined

    Now we have the "All fine now" message followed by undefinedundefined1 and undefinedundefined2.undefinedundefined

    undefinedundefined

    The error in the no-semicolon variant occurs because JavaScript does not assume a semicolon before square brackets undefinedundefined[...].undefinedundefined

    undefinedundefined

    So, because the semicolon is not auto-inserted, the code in the first example is treated as a single statement. Here's how the engine sees it:

    undefinedundefined

    undefinedundefinedjs run no-beautify alert("There will be an error")[1, 2].forEach(alert)undefinedundefined

    undefinedundefined

    But it should be two separate statements, not one. Such a merging in this case is just wrong, hence the error. This can happen in other situations.

    undefinedundefined

    We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again - undefinedundefinedit is possible to leave out semicolons most of the time. But it's safer - especially for a beginner - to use them.undefinedundefined

    undefinedundefined

    Comments [#code-comments]

    undefinedundefined

    As time goes on, programs become more and more complex. It becomes necessary to add undefinedundefinedcomments which describe what the code does and why.undefinedundefined

    undefinedundefined

    Comments can be put into any place of a script. They don't affect its execution because the engine simply ignores them.

    undefinedundefined

    undefinedundefinedOne-line comments start with two forward slash characters undefinedundefined//.undefinedundefinedundefinedundefined

    undefinedundefined

    The rest of the line is a comment. It may occupy a full line of its own or follow a statement.

    undefinedundefined

    Like here: run // This comment occupies a line of its own alert(‘Hello');

    undefinedundefined

    alert(‘World'); // This comment follows the statement

    undefinedundefined

    undefinedundefinedMultiline comments start with a forward slash and an asterisk undefinedundefined/* and end with an asterisk and a forward slash undefinedundefined*/.undefinedundefinedundefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    undefinedundefinedjs run /* An example with two messages. This is a multiline comment. */ alert('Hello'); alert('World');undefinedundefined

    undefinedundefined

    The content of comments is ignored, so if we put code inside undefinedundefined/* … */, it won't execute.undefinedundefined

    undefinedundefined

    Sometimes it can be handy to temporarily disable a part of code:

    undefinedundefined

    undefinedundefinedjs run /* Commenting out the code alert('Hello'); */ alert('World');undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Use hotkeys!" In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl` and `key:Option` instead of `key:Shift`.undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Nested comments are not supported!" There may not be/undefinedundefined/undefinedundefinedinside another/undefinedundefined/`.undefinedundefined

    undefinedundefined

    Such code will die with an error:

    undefinedundefined

    undefinedundefinedjs run no-beautify /* /* nested comment ?!? */ */ alert( 'World' ); undefinedundefined

    undefinedundefined

    Please, don't hesitate to comment your code.

    undefinedundefined

    Comments increase the overall code footprint, but that's not a problem at all. There are many tools which minify code before publishing to a production server. They remove comments, so they don't appear in the working scripts. Therefore, comments do not have negative effects on production at all.

    undefinedundefined

    Later in the tutorial there will be a chapter undefinedundefinedinfo:code-quality that also explains how to write better comments.undefinedundefined

    undefinedundefined

    Property flags and descriptors

    undefinedundefined

    As we know, objects can store properties.

    undefinedundefined

    Until now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing.

    undefinedundefined

    In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions.

    undefinedundefined

    Property flags

    undefinedundefined

    Object properties, besides a undefinedundefinedundefinedundefinedvalueundefinedundefined, have three special attributes (so-called "flags"):undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedundefinedundefinedwritableundefinedundefined - if undefinedundefinedtrue, the value can be changed, otherwise it's read-only.undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefinedundefinedenumerableundefinedundefined - if undefinedundefinedtrue, then listed in loops, otherwise not listed.undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefinedundefinedconfigurableundefinedundefined - if undefinedundefinedtrue, the property can be deleted and these attributes can be modified, otherwise not.undefinedundefined
    • undefinedundefined
    undefinedundefined

    We didn't see them yet, because generally they do not show up. When we create a property "the usual way", all of them are undefinedundefinedtrue. But we also can change them anytime.undefinedundefined

    undefinedundefined

    First, let's see how to get those flags.

    undefinedundefined

    The method undefinedundefinedObject.getOwnPropertyDescriptor allows to query the undefinedundefinedfull information about a property.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefinedobjundefinedundefined
    undefinedundefined
    The object to get information from.
    undefinedundefined
    undefinedundefinedpropertyNameundefinedundefined
    undefinedundefined
    The name of the property.
    undefinedundefined
    undefinedundefined

    The returned value is a so-called "property descriptor" object: it contains the value and all the flags.

    undefinedundefined

    For instance:

    undefinedundefined

    run let user = { name: "John" };

    undefinedundefined

    let descriptor = Object.getOwnPropertyDescriptor(user, ‘name');

    undefinedundefined

    alert( JSON.stringify(descriptor, null, 2 ) ); /* property descriptor: { "value": "John", "writable": true, "enumerable": true, "configurable": true } */

    undefinedundefined

    To change the flags, we can use undefinedundefinedObject.defineProperty.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedObject.undefinedundefineddefineProperty(objundefinedundefined, propertyNameundefinedundefined, descriptor)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefinedobj, undefinedundefinedpropertyNameundefinedundefined
    undefinedundefined
    The object and its property to apply the descriptor.
    undefinedundefined
    undefinedundefineddescriptorundefinedundefined
    undefinedundefined
    Property descriptor object to apply.
    undefinedundefined
    undefinedundefined

    If the property exists, undefinedundefineddefineProperty updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed undefinedundefinedfalse.undefinedundefined

    undefinedundefined

    For instance, here a property undefinedundefinedname is created with all falsy flags:undefinedundefined

    undefinedundefined

    run let user = {};

    undefinedundefined

    undefinedundefined! Object.defineProperty(user, "name", { value: "John" }); undefinedundefined/!undefinedundefined

    undefinedundefined

    let descriptor = Object.getOwnPropertyDescriptor(user, ‘name');

    undefinedundefined

    alert( JSON.stringify(descriptor, null, 2 ) ); /undefinedundefined { "value": "John", !undefinedundefined "writable": false, "enumerable": false, "configurable": false /!undefinedundefined } / undefinedundefined

    undefinedundefined

    Compare it with "normally created" undefinedundefineduser.name above: now all flags are falsy. If that's not what we want then we'd better set them to undefinedundefinedtrue in undefinedundefineddescriptor.undefinedundefined

    undefinedundefined

    Now let's see effects of the flags by example.

    undefinedundefined

    Non-writable

    undefinedundefined

    Let's make undefinedundefineduser.name non-writable (can't be reassigned) by changing undefinedundefinedwritable flag:undefinedundefined

    undefinedundefined

    run let user = { name: "John" };

    undefinedundefined

    Object.defineProperty(user, "name", { undefinedundefined! writable: false undefinedundefined/! });undefinedundefined

    undefinedundefined

    undefinedundefined! user.name = "Pete"; // Error: Cannot assign to read only property ‘name' undefinedundefined/! undefinedundefined

    undefinedundefined

    Now no one can change the name of our user, unless they apply their own undefinedundefineddefineProperty to override ours.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Errors appear only in strict mode" In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.undefinedundefined

    undefinedundefined

    Here's the same example, but the property is created from scratch:

    undefinedundefined

    run let user = { };

    undefinedundefined

    Object.defineProperty(user, "name", { undefinedundefined! value: "John", // for new properties we need to explicitly list what's true enumerable: true, configurable: true undefinedundefined/! });undefinedundefined

    undefinedundefined

    alert(user.name); // John user.name = "Pete"; // Error

    undefinedundefined

    Non-enumerable

    undefinedundefined

    Now let's add a custom undefinedundefinedtoString to undefinedundefineduser.undefinedundefined

    undefinedundefined

    Normally, a built-in undefinedundefinedtoString for objects is non-enumerable, it does not show up in undefinedundefinedfor..in. But if we add a undefinedundefinedtoString of our own, then by default it shows up in undefinedundefinedfor..in, like this:undefinedundefined

    undefinedundefined

    run let user = { name: "John", toString() { return this.name; } };

    undefinedundefined

    // By default, both our properties are listed: for (let key in user) alert(key); // name, toString

    undefinedundefined

    If we don't like it, then we can set undefinedundefinedenumerable:false. Then it won't appear in a undefinedundefinedfor..in loop, just like the built-in one:undefinedundefined

    undefinedundefined

    run let user = { name: "John", toString() { return this.name; } };

    undefinedundefined

    Object.defineProperty(user, "toString", { undefinedundefined! enumerable: false undefinedundefined/! });undefinedundefined

    undefinedundefined

    undefinedundefined! // Now our toString disappears: undefinedundefined/! for (let key in user) alert(key); // name undefinedundefined

    undefinedundefined

    Non-enumerable properties are also excluded from undefinedundefinedObject.keys:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedalert(undefinedundefinedObject.undefinedundefinedkeys(user))undefinedundefined;undefinedundefined// nameundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Non-configurable

    undefinedundefined

    The non-configurable flag (undefinedundefinedconfigurable:false) is sometimes preset for built-in objects and properties.undefinedundefined

    undefinedundefined

    A non-configurable property can not be deleted.

    undefinedundefined

    For instance, undefinedundefinedMath.PI is non-writable, non-enumerable and non-configurable:undefinedundefined

    undefinedundefined

    run let descriptor = Object.getOwnPropertyDescriptor(Math, ‘PI');

    undefinedundefined

    alert( JSON.stringify(descriptor, null, 2 ) ); /undefinedundefined { "value": 3.141592653589793, "writable": false, "enumerable": false, "configurable": false } / ``undefinedundefinedSo, a programmer is unable to change the value ofMath.PI` or overwrite it.undefinedundefined

    undefinedundefined

    run Math.PI = 3; // Error

    undefinedundefined

    // delete Math.PI won't work either

    undefinedundefined

    Making a property non-configurable is a one-way road. We cannot change it back with undefinedundefineddefineProperty.undefinedundefined

    undefinedundefined

    To be precise, non-configurability imposes several restrictions on undefinedundefineddefineProperty: 1. Can't change undefinedundefinedconfigurable flag. 2. Can't change undefinedundefinedenumerable flag. 3. Can't change undefinedundefinedwritable: false to undefinedundefinedtrue (the other way round works). 4. Can't change undefinedundefinedget/set for an accessor property (but can assign them if absent).undefinedundefined

    undefinedundefined

    undefinedundefinedThe idea of "configurable: false" is to prevent changes of property flags and its deletion, while allowing to change its value.undefinedundefined

    undefinedundefined

    Here undefinedundefineduser.name is non-configurable, but we can still change it (as it's writable):undefinedundefined

    undefinedundefined

    run let user = { name: "John" };

    undefinedundefined

    Object.defineProperty(user, "name", { configurable: false });

    undefinedundefined

    user.name = "Pete"; // works fine delete user.name; // Error

    undefinedundefined

    And here we make undefinedundefineduser.name a "forever sealed" constant:undefinedundefined

    undefinedundefined

    run let user = { name: "John" };

    undefinedundefined

    Object.defineProperty(user, "name", { writable: false, configurable: false });

    undefinedundefined

    // won't be able to change user.name or its flags // all this won't work: user.name = "Pete"; delete user.name; Object.defineProperty(user, "name", { value: "Pete" });

    undefinedundefined

    Object.defineProperties

    undefinedundefined

    There's a method undefinedundefinedObject.defineProperties(obj, descriptors) that allows to define many properties at once.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    So, we can set many properties at once.

    undefinedundefined

    Object.getOwnPropertyDescriptors

    undefinedundefined

    To get all property descriptors at once, we can use the method undefinedundefinedObject.getOwnPropertyDescriptors(obj).undefinedundefined

    undefinedundefined

    Together with undefinedundefinedObject.defineProperties it can be used as a "flags-aware" way of cloning an object:undefinedundefined

    undefinedundefinedundefinedundefined

    Normally when we clone an object, we use an assignment to copy properties, like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfor (undefinedundefinedlet key undefinedundefinedin user) undefinedundefined{undefinedundefinedundefinedundefined  clone[key] undefinedundefined= user[key]undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    …But that does not copy flags. So if we want a "better" clone then undefinedundefinedObject.defineProperties is preferred.undefinedundefined

    undefinedundefined

    Another difference is that undefinedundefinedfor..in ignores symbolic properties, but undefinedundefinedObject.getOwnPropertyDescriptors returns undefinedundefinedall property descriptors including symbolic ones.undefinedundefined

    undefinedundefined

    Sealing an object globally

    undefinedundefined

    Property descriptors work at the level of individual properties.

    undefinedundefined

    There are also methods that limit access to the undefinedundefinedwhole object:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedObject.preventExtensions(obj)undefinedundefined
    undefinedundefined
    Forbids the addition of new properties to the object.
    undefinedundefined
    undefinedundefinedObject.seal(obj)undefinedundefined
    undefinedundefined
    Forbids adding/removing of properties. Sets undefinedundefinedconfigurable: false for all existing properties. undefinedundefined
    undefinedundefined
    undefinedundefinedObject.freeze(obj)undefinedundefined
    undefinedundefined
    Forbids adding/removing/changing of properties. Sets undefinedundefinedconfigurable: false, writable: false for all existing properties. undefinedundefined
    undefinedundefined
    undefinedundefined

    And also there are tests for them:

    undefinedundefined
    undefinedundefined
    undefinedundefinedObject.isExtensible(obj)undefinedundefined
    undefinedundefined
    Returns undefinedundefinedfalse if adding properties is forbidden, otherwise undefinedundefinedtrue. undefinedundefined
    undefinedundefined
    undefinedundefinedObject.isSealed(obj)undefinedundefined
    undefinedundefined
    Returns undefinedundefinedtrue if adding/removing properties is forbidden, and all existing properties have undefinedundefinedconfigurable: false. undefinedundefined
    undefinedundefined
    undefinedundefinedObject.isFrozen(obj)undefinedundefined
    undefinedundefined
    Returns undefinedundefinedtrue if adding/removing/changing properties is forbidden, and all current properties are undefinedundefinedconfigurable: false, writable: false. undefinedundefined
    undefinedundefined
    undefinedundefined

    These methods are rarely used in practice.

    undefinedundefined

    Property getters and setters

    undefinedundefined

    There are two kinds of object properties.

    undefinedundefined

    The first kind is undefinedundefineddata properties. We already know how to work with them. All properties that we've been using until now were data properties.undefinedundefined

    undefinedundefined

    The second type of properties is something new. It's undefinedundefinedaccessor properties. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.undefinedundefined

    undefinedundefined

    Getters and setters

    undefinedundefined

    Accessor properties are represented by "getter" and "setter" methods. In an object literal they are denoted by undefinedundefinedget and undefinedundefinedset:undefinedundefined

    undefinedundefinedundefinedundefined

    The getter works when undefinedundefinedobj.propName is read, the setter - when it is assigned.undefinedundefined

    undefinedundefined

    For instance, we have a undefinedundefineduser object with undefinedundefinedname and undefinedundefinedsurname:undefinedundefined

    undefinedundefinedundefinedundefined

    Now we want to add a undefinedundefinedfullName property, that should be undefinedundefined"John Smith". Of course, we don't want to copy-paste existing information, so we can implement it as an accessor:undefinedundefined

    undefinedundefined

    run let user = { name: "John", surname: "Smith",

    undefinedundefined

    undefinedundefined! get fullName() { return undefinedundefined${this.name} ${this.surname}; } undefinedundefined/! };undefinedundefined

    undefinedundefined

    undefinedundefined! alert(user.fullName); // John Smith undefinedundefined/! undefinedundefined

    undefinedundefined

    From the outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't undefinedundefinedcallundefinedundefineduser.fullName as a function, we undefinedundefinedread it normally: the getter runs behind the scenes.undefinedundefined

    undefinedundefined

    As of now, undefinedundefinedfullName has only a getter. If we attempt to assign undefinedundefineduser.fullName=, there will be an error:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let user = { get fullName() { return…`; } };undefinedundefined

    undefinedundefined

    undefinedundefined! user.fullName = "Test"; // Error (property has only a getter) undefinedundefined/! undefinedundefined

    undefinedundefined

    Let's fix it by adding a setter for undefinedundefineduser.fullName:undefinedundefined

    undefinedundefined

    run let user = { name: "John", surname: "Smith",

    undefinedundefined

    get fullName() { return undefinedundefined${this.name} ${this.surname}; },undefinedundefined

    undefinedundefined

    undefinedundefined! set fullName(value) { [this.name, this.surname] = value.split(" "); } undefinedundefined/! };undefinedundefined

    undefinedundefined

    // set fullName is executed with the given value. user.fullName = "Alice Cooper";

    undefinedundefined

    alert(user.name); // Alice alert(user.surname); // Cooper

    undefinedundefined

    As the result, we have a "virtual" property undefinedundefinedfullName. It is readable and writable.undefinedundefined

    undefinedundefined

    Accessor descriptors

    undefinedundefined

    Descriptors for accessor properties are different from those for data properties.

    undefinedundefined

    For accessor properties, there is no undefinedundefinedvalue or undefinedundefinedwritable, but instead there are undefinedundefinedget and undefinedundefinedset functions.undefinedundefined

    undefinedundefined

    That is, an accessor descriptor may have:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedundefinedundefinedgetundefinedundefined - a function without arguments, that works when a property is read,undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefinedundefinedsetundefinedundefined - a function with one argument, that is called when the property is set,undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefinedundefinedenumerableundefinedundefined - same as for data properties,undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefinedundefinedconfigurableundefinedundefined - same as for data properties.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance, to create an accessor undefinedundefinedfullName with undefinedundefineddefineProperty, we can pass a descriptor with undefinedundefinedget and undefinedundefinedset:undefinedundefined

    undefinedundefined

    run let user = { name: "John", surname: "Smith" };

    undefinedundefined

    undefinedundefined! Object.defineProperty(user, ‘fullName', { get() { return undefinedundefined${this.name} ${this.surname}; },undefinedundefined

    undefinedundefined

    set(value) { [this.name, this.surname] = value.split(" "); } undefinedundefined/! });undefinedundefined

    undefinedundefined

    alert(user.fullName); // John Smith

    undefinedundefined

    for(let key in user) alert(key); // name, surname

    undefinedundefined

    Please note that a property can be either an accessor (has undefinedundefinedget/set methods) or a data property (has a undefinedundefinedvalue), not both.undefinedundefined

    undefinedundefined

    If we try to supply both undefinedundefinedget and undefinedundefinedvalue in the same descriptor, there will be an error:undefinedundefined

    undefinedundefined

    run undefinedundefined! // Error: Invalid property descriptor. undefinedundefined/! Object.defineProperty({}, ‘prop', { get() { return 1 },undefinedundefined

    undefinedundefined

    value: 2 });

    undefinedundefined

    Smarter getters/setters

    undefinedundefined

    Getters/setters can be used as wrappers over "real" property values to gain more control over operations with them.

    undefinedundefined

    For instance, if we want to forbid too short names for undefinedundefineduser, we can have a setter undefinedundefinedname and keep the value in a separate property undefinedundefined_name:undefinedundefined

    undefinedundefined

    run let user = { get name() { return this._name; },

    undefinedundefined

    set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; } };

    undefinedundefined

    user.name = "Pete"; alert(user.name); // Pete

    undefinedundefined

    user.name = ""; // Name is too short…

    undefinedundefined

    So, the name is stored in undefinedundefined_name property, and the access is done via getter and setter.undefinedundefined

    undefinedundefined

    Technically, external code is able to access the name directly by using undefinedundefineduser._name. But there is a widely known convention that properties starting with an underscore undefinedundefined"_" are internal and should not be touched from outside the object.undefinedundefined

    undefinedundefined

    Using for compatibility

    undefinedundefined

    One of the great uses of accessors is that they allow to take control over a "regular" data property at any moment by replacing it with a getter and a setter and tweak its behavior.

    undefinedundefined

    Imagine we started implementing user objects using data properties undefinedundefinedname and undefinedundefinedage:undefinedundefined

    undefinedundefinedundefinedundefined

    …But sooner or later, things may change. Instead of undefinedundefinedage we may decide to store undefinedundefinedbirthday, because it's more precise and convenient:undefinedundefined

    undefinedundefinedundefinedundefined

    Now what to do with the old code that still uses undefinedundefinedage property?undefinedundefined

    undefinedundefined

    We can try to find all such places and fix them, but that takes time and can be hard to do if that code is used by many other people. And besides, undefinedundefinedage is a nice thing to have in undefinedundefineduser, right?undefinedundefined

    undefinedundefined

    Let's keep it.

    undefinedundefined

    Adding a getter for undefinedundefinedage solves the problem:undefinedundefined

    undefinedundefined

    run no-beautify function User(name, birthday) { this.name = name; this.birthday = birthday;

    undefinedundefined

    undefinedundefined! // age is calculated from the current date and birthday Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); } }); undefinedundefined/! }undefinedundefined

    undefinedundefined

    let john = new User("John", new Date(1992, 6, 1));

    undefinedundefined

    alert( john.birthday ); // birthday is available alert( john.age ); // …as well as the age

    undefinedundefined

    Now the old code works too and we've got a nice additional property.

    undefinedundefined

    Prototypal inheritance

    undefinedundefined

    In programming, we often want to take something and extend it.

    undefinedundefined

    For instance, we have a undefinedundefineduser object with its properties and methods, and want to make undefinedundefinedadmin and undefinedundefinedguest as slightly modified variants of it. We'd like to reuse what we have in undefinedundefineduser, not copy/reimplement its methods, just build a new object on top of it.undefinedundefined

    undefinedundefined

    undefinedundefinedPrototypal inheritance is a language feature that helps in that.undefinedundefined

    undefinedundefined

    [[Prototype]]

    undefinedundefined

    In JavaScript, objects have a special hidden property undefinedundefined[[Prototype]] (as named in the specification), that is either undefinedundefinednull or references another object. That object is called "a prototype":undefinedundefined

    undefinedundefined
    undefinedundefinedprototypeundefinedundefined
    prototype
    undefinedundefined
    undefinedundefined

    When we read a property from undefinedundefinedobject, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it.undefinedundefined

    undefinedundefined

    The property undefinedundefined[[Prototype]] is internal and hidden, but there are many ways to set it.undefinedundefined

    undefinedundefined

    One of them is to use the special name undefinedundefined__proto__, like this:undefinedundefined

    undefinedundefined

    run let animal = { eats: true }; let rabbit = { jumps: true };

    undefinedundefined

    undefinedundefined! rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal undefinedundefined/! undefinedundefined

    undefinedundefined

    Now if we read a property from undefinedundefinedrabbit, and it's missing, JavaScript will automatically take it from undefinedundefinedanimal.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet animal undefinedundefined=undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedeatsundefinedundefined:undefinedundefinedtrueundefinedundefinedundefinedundefinedundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefinedlet rabbit undefinedundefined=undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedjumpsundefinedundefined:undefinedundefinedtrueundefinedundefinedundefinedundefinedundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefinedrabbit.undefinedundefined__proto__undefinedundefined= animalundefinedundefined;undefinedundefined// (*)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// we can find both properties in rabbit now:undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedalertundefinedundefined(undefinedundefined rabbit.eats undefinedundefined)undefinedundefined; // true undefinedundefined(**)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefinedundefinedundefinedalert( undefinedundefinedrabbit.undefinedundefinedjumps )undefinedundefined;undefinedundefined// trueundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Here the line undefinedundefined(*) sets undefinedundefinedanimal to be a prototype of undefinedundefinedrabbit.undefinedundefined

    undefinedundefined

    Then, when undefinedundefinedalert tries to read property undefinedundefinedrabbit.eatsundefinedundefined(**), it's not in undefinedundefinedrabbit, so JavaScript follows the undefinedundefined[[Prototype]] reference and finds it in undefinedundefinedanimal (look from the bottom up):undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Here we can say that "undefinedundefinedanimal is the prototype of undefinedundefinedrabbit" or "undefinedundefinedrabbit prototypically inherits from undefinedundefinedanimal".undefinedundefined

    undefinedundefined

    So if undefinedundefinedanimal has a lot of useful properties and methods, then they become automatically available in undefinedundefinedrabbit. Such properties are called "inherited".undefinedundefined

    undefinedundefined

    If we have a method in undefinedundefinedanimal, it can be called on undefinedundefinedrabbit:undefinedundefined

    undefinedundefined

    run let animal = { eats: true, undefinedundefined! walk() { alert("Animal walk"); } undefinedundefined/! };undefinedundefined

    undefinedundefined

    let rabbit = { jumps: true, undefinedundefinedproto: animal };undefinedundefined

    undefinedundefined

    // walk is taken from the prototype undefinedundefined! rabbit.walk(); // Animal walk undefinedundefined/! undefinedundefined

    undefinedundefined

    The method is automatically taken from the prototype, like this:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The prototype chain can be longer:

    undefinedundefined

    run let animal = { eats: true, walk() { alert("Animal walk"); } };

    undefinedundefined

    let rabbit = { jumps: true, undefinedundefined!undefinedundefinedproto: animal undefinedundefined/! };undefinedundefined

    undefinedundefined

    let longEar = { earLength: 10, undefinedundefined!undefinedundefinedproto: rabbit undefinedundefined/! };undefinedundefined

    undefinedundefined

    // walk is taken from the prototype chain longEar.walk(); // Animal walk alert(longEar.jumps); // true (from rabbit)

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Now if we read something from undefinedundefinedlongEar, and it's missing, JavaScript will look for it in undefinedundefinedrabbit, and then in undefinedundefinedanimal.undefinedundefined

    undefinedundefined

    There are only two limitations:

    undefinedundefined
      undefinedundefined
    1. The references can't go in circles. JavaScript will throw an error if we try to assign undefinedundefined__proto__ in a circle.undefinedundefined
    2. undefinedundefined
    3. The value of undefinedundefined__proto__ can be either an object or undefinedundefinednull. Other types are ignored.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Also it may be obvious, but still: there can be only one undefinedundefined[[Prototype]]. An object may not inherit from two others.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="undefinedundefinedprotoundefinedundefinedis a historical getter/setter for[[Prototype]]`" It's a common mistake of novice developers not to know the difference between these two.undefinedundefined

    undefinedundefined

    Please note that undefinedundefined__proto__ is undefinedundefinednot the same as the internal undefinedundefined[[Prototype]] property. It's a getter/setter for undefinedundefined[[Prototype]]. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language.undefinedundefined

    undefinedundefined

    The undefinedundefined__proto__ property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use undefinedundefinedObject.getPrototypeOf/Object.setPrototypeOf functions instead that get/set the prototype. We'll also cover these functions later.undefinedundefined

    undefinedundefined

    By the specification, undefinedundefined__proto__ must only be supported by browsers. In fact though, all environments including server-side support undefinedundefined__proto__, so we're quite safe using it.undefinedundefined

    undefinedundefined

    As the undefinedundefined__proto__ notation is a bit more intuitively obvious, we use it in the examples. undefinedundefined

    undefinedundefined

    Writing doesn't use prototype

    undefinedundefined

    The prototype is only used for reading properties.

    undefinedundefined

    Write/delete operations work directly with the object.

    undefinedundefined

    In the example below, we assign its own undefinedundefinedwalk method to undefinedundefinedrabbit:undefinedundefined

    undefinedundefined

    run let animal = { eats: true, walk() { /* this method won't be used by rabbit */undefinedundefined
    } };undefinedundefined

    undefinedundefined

    let rabbit = { undefinedundefinedproto: animal };undefinedundefined

    undefinedundefined

    undefinedundefined! rabbit.walk = function() { alert("Rabbit! Bounce-bounce!"); }; undefinedundefined/!undefinedundefined

    undefinedundefined

    rabbit.walk(); // Rabbit! Bounce-bounce!

    undefinedundefined

    From now on, undefinedundefinedrabbit.walk() call finds the method immediately in the object and executes it, without using the prototype:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Accessor properties are an exception, as assignment is handled by a setter function. So writing to such a property is actually the same as calling a function.

    undefinedundefined

    For that reason undefinedundefinedadmin.fullName works correctly in the code below:undefinedundefined

    undefinedundefined

    run let user = { name: "John", surname: "Smith",

    undefinedundefined

    set fullName(value) { [this.name, this.surname] = value.split(" "); },

    undefinedundefined

    get fullName() { return undefinedundefined${this.name} ${this.surname}; } };undefinedundefined

    undefinedundefined

    let admin = { undefinedundefinedproto: user, isAdmin: true };undefinedundefined

    undefinedundefined

    alert(admin.fullName); // John Smith (*)

    undefinedundefined

    // setter triggers! admin.fullName = "Alice Cooper"; // (**)

    undefinedundefined

    alert(admin.fullName); // Alice Cooper, state of admin modified alert(user.fullName); // John Smith, state of user protected

    undefinedundefined

    Here in the line undefinedundefined(*) the property undefinedundefinedadmin.fullName has a getter in the prototype undefinedundefineduser, so it is called. And in the line undefinedundefined(**) the property has a setter in the prototype, so it is called.undefinedundefined

    undefinedundefined

    The value of "this"

    undefinedundefined

    An interesting question may arise in the example above: what's the value of undefinedundefinedthis inside undefinedundefinedset fullName(value)? Where are the properties undefinedundefinedthis.name and undefinedundefinedthis.surname written: into undefinedundefineduser or undefinedundefinedadmin?undefinedundefined

    undefinedundefined

    The answer is simple: undefinedundefinedthis is not affected by prototypes at all.undefinedundefined

    undefinedundefined

    undefinedundefinedNo matter where the method is found: in an object or its prototype. In a method call, undefinedundefinedthis is always the object before the dot.undefinedundefinedundefinedundefined

    undefinedundefined

    So, the setter call undefinedundefinedadmin.fullName= uses undefinedundefinedadmin as undefinedundefinedthis, not undefinedundefineduser.undefinedundefined

    undefinedundefined

    That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object.

    undefinedundefined

    For instance, here undefinedundefinedanimal represents a "method storage", and undefinedundefinedrabbit makes use of it.undefinedundefined

    undefinedundefined

    The call undefinedundefinedrabbit.sleep() sets undefinedundefinedthis.isSleeping on the undefinedundefinedrabbit object:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run // animal has methods let animal = { walk() { if (!this.isSleeping) { alert(I walk`); } }, sleep() { this.isSleeping = true; } };undefinedundefined

    undefinedundefined

    let rabbit = { name: "White Rabbit", undefinedundefinedproto: animal };undefinedundefined

    undefinedundefined

    // modifies rabbit.isSleeping rabbit.sleep();

    undefinedundefined

    alert(rabbit.isSleeping); // true alert(animal.isSleeping); // undefined (no such property in the prototype)

    undefinedundefined

    The resulting picture:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    If we had other objects, like undefinedundefinedbird, undefinedundefinedsnake, etc., inheriting from undefinedundefinedanimal, they would also gain access to methods of undefinedundefinedanimal. But undefinedundefinedthis in each method call would be the corresponding object, evaluated at the call-time (before dot), not undefinedundefinedanimal. So when we write data into undefinedundefinedthis, it is stored into these objects.undefinedundefined

    undefinedundefined

    As a result, methods are shared, but the object state is not.

    undefinedundefined

    for..in loop

    undefinedundefined

    The undefinedundefinedfor..in loop iterates over inherited properties too.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let animal = { eats: true };

    undefinedundefined

    let rabbit = { jumps: true, undefinedundefinedproto: animal };undefinedundefined

    undefinedundefined

    undefinedundefined! // Object.keys only returns own keys alert(Object.keys(rabbit)); // jumps undefinedundefined/!undefinedundefined

    undefinedundefined

    undefinedundefined! // for..in loops over both own and inherited keys for(let prop in rabbit) alert(prop); // jumps, then eats undefinedundefined/! undefinedundefined

    undefinedundefined

    If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method undefinedundefinedobj.hasOwnProperty(key): it returns undefinedundefinedtrue if undefinedundefinedobj has its own (not inherited) property named undefinedundefinedkey.undefinedundefined

    undefinedundefined

    So we can filter out inherited properties (or do something else with them):

    undefinedundefined

    run let animal = { eats: true };

    undefinedundefined

    let rabbit = { jumps: true, undefinedundefinedproto: animal };undefinedundefined

    undefinedundefined

    for(let prop in rabbit) { let isOwn = rabbit.hasOwnProperty(prop);

    undefinedundefined

    if (isOwn) { alert(undefinedundefinedOur: ${prop}); // Our: jumps } else { alert(undefinedundefinedInherited: ${prop}); // Inherited: eats } } undefinedundefined

    undefinedundefined

    Here we have the following inheritance chain: undefinedundefinedrabbit inherits from undefinedundefinedanimal, that inherits from undefinedundefinedObject.prototype (because undefinedundefinedanimal is a literal object undefinedundefined{...}, so it's by default), and then undefinedundefinednull above it:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Note, there's one funny thing. Where is the method undefinedundefinedrabbit.hasOwnProperty coming from? We did not define it. Looking at the chain we can see that the method is provided by undefinedundefinedObject.prototype.hasOwnProperty. In other words, it's inherited.undefinedundefined

    undefinedundefined

    …But why does undefinedundefinedhasOwnProperty not appear in the undefinedundefinedfor..in loop like undefinedundefinedeats and undefinedundefinedjumps do, if undefinedundefinedfor..in lists inherited properties?undefinedundefined

    undefinedundefined

    The answer is simple: it's not enumerable. Just like all other properties of undefinedundefinedObject.prototype, it has undefinedundefinedenumerable:false flag. And undefinedundefinedfor..in only lists enumerable properties. That's why it and the rest of the undefinedundefinedObject.prototype properties are not listed.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Almost all other key/value-getting methods ignore inherited properties" Almost all other key/value-getting methods, such asObject.keysundefinedundefined,Object.values` and so on ignore inherited properties.undefinedundefined

    undefinedundefined

    They only operate on the object itself. Properties from the prototype are undefinedundefinednot taken into account. undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • In JavaScript, all objects have a hidden undefinedundefined[[Prototype]] property that's either another object or undefinedundefinednull.undefinedundefined
    • undefinedundefined
    • We can use undefinedundefinedobj.__proto__ to access it (a historical getter/setter, there are other ways, to be covered soon).undefinedundefined
    • undefinedundefined
    • The object referenced by undefinedundefined[[Prototype]] is called a "prototype".undefinedundefined
    • undefinedundefined
    • If we want to read a property of undefinedundefinedobj or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype.undefinedundefined
    • undefinedundefined
    • Write/delete operations act directly on the object, they don't use the prototype (assuming it's a data property, not a setter).
    • undefinedundefined
    • If we call undefinedundefinedobj.method(), and the undefinedundefinedmethod is taken from the prototype, undefinedundefinedthis still references undefinedundefinedobj. So methods always work with the current object even if they are inherited.undefinedundefined
    • undefinedundefined
    • The undefinedundefinedfor..in loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself.undefinedundefined
    • undefinedundefined
    undefinedundefined

    F.prototype

    undefinedundefined

    Remember, new objects can be created with a constructor function, like undefinedundefinednew F().undefinedundefined

    undefinedundefined

    If undefinedundefinedF.prototype is an object, then the undefinedundefinednew operator uses it to set undefinedundefined[[Prototype]] for the new object.undefinedundefined

    undefinedundefined
    undefinedundefinedJavaScript had prototypal inheritance from the beginning. It was one of the core features of the language.
    But in the old times, there was no direct access to it. The only thing that worked reliably was a `"prototype"` property of the constructor function, described in this chapter. So there are many scripts that still use it.undefinedundefined
    undefinedundefined

    Please note that undefinedundefinedF.prototype here means a regular property named undefinedundefined"prototype" on undefinedundefinedF. It sounds something similar to the term "prototype", but here we really mean a regular property with this name.undefinedundefined

    undefinedundefined

    Here's the example:

    undefinedundefined

    run let animal = { eats: true };

    undefinedundefined

    function Rabbit(name) { this.name = name; }

    undefinedundefined

    undefinedundefined! Rabbit.prototype = animal; undefinedundefined/!undefinedundefined

    undefinedundefined

    let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal

    undefinedundefined

    alert( rabbit.eats ); // true

    undefinedundefined

    Setting undefinedundefinedRabbit.prototype = animal literally states the following: "When a undefinedundefinednew Rabbit is created, assign its undefinedundefined[[Prototype]] to undefinedundefinedanimal".undefinedundefined

    undefinedundefined

    That's the resulting picture:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    On the picture, undefinedundefined"prototype" is a horizontal arrow, meaning a regular property, and undefinedundefined[[Prototype]] is vertical, meaning the inheritance of undefinedundefinedrabbit from undefinedundefinedanimal.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="F.prototypeundefinedundefinedonly used atnew Fundefinedundefinedtime"F.prototypeundefinedundefinedproperty is only used whennew Fundefinedundefinedis called, it assigns[[Prototype]]` of the new object.undefinedundefined

    undefinedundefined

    If, after the creation, undefinedundefinedF.prototype property changes (undefinedundefinedF.prototype = <another object>), then new objects created by undefinedundefinednew F will have another object as undefinedundefined[[Prototype]], but already existing objects keep the old one. undefinedundefined

    undefinedundefined

    Default F.prototype, constructor property

    undefinedundefined

    Every function has the undefinedundefined"prototype" property even if we don't supply it.undefinedundefined

    undefinedundefined

    The default undefinedundefined"prototype" is an object with the only property undefinedundefinedconstructor that points back to the function itself.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedRabbit() undefinedundefined{}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined/* default prototypeundefinedundefinedundefinedundefinedundefinedundefinedRabbit.prototype = { constructor: Rabbit };undefinedundefinedundefinedundefinedundefinedundefined*/undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    We can check it:

    undefinedundefined

    run function Rabbit() {} // by default: // Rabbit.prototype = { constructor: Rabbit }

    undefinedundefined

    alert( Rabbit.prototype.constructor == Rabbit ); // true

    undefinedundefined

    Naturally, if we do nothing, the undefinedundefinedconstructor property is available to all rabbits through undefinedundefined[[Prototype]]:undefinedundefined

    undefinedundefined

    run function Rabbit() {} // by default: // Rabbit.prototype = { constructor: Rabbit }

    undefinedundefined

    let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}

    undefinedundefined

    alert(rabbit.constructor == Rabbit); // true (from prototype)

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    We can use undefinedundefinedconstructor property to create a new object using the same constructor as the existing one.undefinedundefined

    undefinedundefined

    Like here:

    undefinedundefined

    run function Rabbit(name) { this.name = name; alert(name); }

    undefinedundefined

    let rabbit = new Rabbit("White Rabbit");

    undefinedundefined

    undefinedundefined! let rabbit2 = new rabbit.constructor("Black Rabbit"); undefinedundefined/! undefinedundefined

    undefinedundefined

    That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind.

    undefinedundefined

    But probably the most important thing about undefinedundefined"constructor" is that…undefinedundefined

    undefinedundefined

    undefinedundefined…JavaScript itself does not ensure the right undefinedundefined"constructor" value.undefinedundefinedundefinedundefined

    undefinedundefined

    Yes, it exists in the default undefinedundefined"prototype" for functions, but that's all. What happens with it later - is totally on us.undefinedundefined

    undefinedundefined

    In particular, if we replace the default prototype as a whole, then there will be no undefinedundefined"constructor" in it.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run function Rabbit() {} Rabbit.prototype = { jumps: true };

    undefinedundefined

    let rabbit = new Rabbit(); undefinedundefined! alert(rabbit.constructor === Rabbit); // false undefinedundefined/! undefinedundefined

    undefinedundefined

    So, to keep the right undefinedundefined"constructor" we can choose to add/remove properties to the default undefinedundefined"prototype" instead of overwriting it as a whole:undefinedundefined

    undefinedundefinedundefinedundefined

    Or, alternatively, recreate the undefinedundefinedconstructor property manually:undefinedundefined

    undefinedundefinedundefinedundefined

    Summary

    undefinedundefined

    In this chapter we briefly described the way of setting a undefinedundefined[[Prototype]] for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it.undefinedundefined

    undefinedundefined

    Everything is quite simple, just a few notes to make things clear:

    undefinedundefined
      undefinedundefined
    • The undefinedundefinedF.prototype property (don't mistake it for undefinedundefined[[Prototype]]) sets undefinedundefined[[Prototype]] of new objects when undefinedundefinednew F() is called.undefinedundefined
    • undefinedundefined
    • The value of undefinedundefinedF.prototype should be either an object or undefinedundefinednull: other values won't work.undefinedundefined
    • undefinedundefined
    • The undefinedundefined"prototype" property only has such a special effect when set on a constructor function, and invoked with undefinedundefinednew.undefinedundefined
    • undefinedundefined
    undefinedundefined

    On regular objects the undefinedundefinedprototype is nothing special:undefinedundefined

    undefinedundefinedundefinedundefined

    By default all functions have undefinedundefinedF.prototype = { constructor: F }, so we can get the constructor of an object by accessing its undefinedundefined"constructor" property.undefinedundefined

    undefinedundefined

    Native prototypes

    undefinedundefined

    The undefinedundefined"prototype" property is widely used by the core of JavaScript itself. All built-in constructor functions use it.undefinedundefined

    undefinedundefined

    First we'll see at the details, and then how to use it for adding new capabilities to built-in objects.

    undefinedundefined

    Object.prototype

    undefinedundefined

    Let's say we output an empty object:

    undefinedundefined

    undefinedundefinedjs run let obj = {}; alert( obj ); // "[object Object]" ?undefinedundefined

    undefinedundefined

    Where's the code that generates the string undefinedundefined"[object Object]"? That's a built-in undefinedundefinedtoString method, but where is it? The undefinedundefinedobj is empty!undefinedundefined

    undefinedundefined

    …But the short notation undefinedundefinedobj = {} is the same as undefinedundefinedobj = new Object(), where undefinedundefinedObject is a built-in object constructor function, with its own undefinedundefinedprototype referencing a huge object with undefinedundefinedtoString and other methods.undefinedundefined

    undefinedundefined

    Here's what's going on:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    When undefinedundefinednew Object() is called (or a literal object undefinedundefined{...} is created), the undefinedundefined[[Prototype]] of it is set to undefinedundefinedObject.prototype according to the rule that we discussed in the previous chapter:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    So then when undefinedundefinedobj.toString() is called the method is taken from undefinedundefinedObject.prototype.undefinedundefined

    undefinedundefined

    We can check it like this:

    undefinedundefined

    run let obj = {};

    undefinedundefined

    alert(obj.__proto__ === Object.prototype); // true

    undefinedundefined

    alert(obj.toString === obj.__proto__.toString); //true alert(obj.toString === Object.prototype.toString); //true

    undefinedundefined

    Please note that there is no more undefinedundefined[[Prototype]] in the chain above undefinedundefinedObject.prototype:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert(Object.prototype.__proto__); // nullundefinedundefined

    undefinedundefined

    Other built-in prototypes

    undefinedundefined

    Other built-in objects such as undefinedundefinedArray, undefinedundefinedDate, undefinedundefinedFunction and others also keep methods in prototypes.undefinedundefined

    undefinedundefined

    For instance, when we create an array undefinedundefined[1, 2, 3], the default undefinedundefinednew Array() constructor is used internally. So undefinedundefinedArray.prototype becomes its prototype and provides methods. That's very memory-efficient.undefinedundefined

    undefinedundefined

    By specification, all of the built-in prototypes have undefinedundefinedObject.prototype on the top. That's why some people say that "everything inherits from objects".undefinedundefined

    undefinedundefined

    Here's the overall picture (for 3 built-ins to fit):

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Let's check the prototypes manually:

    undefinedundefined

    run let arr = [1, 2, 3];

    undefinedundefined

    // it inherits from Array.prototype? alert( arr.__proto__ === Array.prototype ); // true

    undefinedundefined

    // then from Object.prototype? alert( arr.__proto__.__proto__ === Object.prototype ); // true

    undefinedundefined

    // and null on the top. alert( arr.__proto__.__proto__.__proto__ ); // null

    undefinedundefined

    Some methods in prototypes may overlap, for instance, undefinedundefinedArray.prototype has its own undefinedundefinedtoString that lists comma-delimited elements:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let arr = [1, 2, 3] alert(arr); // 1,2,3 <-- the result of Array.prototype.toStringundefinedundefined

    undefinedundefined

    As we've seen before, undefinedundefinedObject.prototype has undefinedundefinedtoString as well, but undefinedundefinedArray.prototype is closer in the chain, so the array variant is used.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    In-browser tools like Chrome developer console also show inheritance (undefinedundefinedconsole.dir may need to be used for built-in objects):undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Other built-in objects also work the same way. Even functions - they are objects of a built-in undefinedundefinedFunction constructor, and their methods (undefinedundefinedcall/undefinedundefinedapply and others) are taken from undefinedundefinedFunction.prototype. Functions have their own undefinedundefinedtoString too.undefinedundefined

    undefinedundefined

    run function f() {}

    undefinedundefined

    alert(f.__proto__ == Function.prototype); // true alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects

    undefinedundefined

    Primitives

    undefinedundefined

    The most intricate thing happens with strings, numbers and booleans.

    undefinedundefined

    As we remember, they are not objects. But if we try to access their properties, temporary wrapper objects are created using built-in constructors undefinedundefinedString, undefinedundefinedNumber and undefinedundefinedBoolean. They provide the methods and disappear.undefinedundefined

    undefinedundefined

    These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as undefinedundefinedString.prototype, undefinedundefinedNumber.prototype and undefinedundefinedBoolean.prototype.undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Values `null` and `undefined` have no object wrappers" Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes either.undefinedundefined

    undefinedundefined

    Changing native prototypes [#native-prototype-change]

    undefinedundefined

    Native prototypes can be modified. For instance, if we add a method to undefinedundefinedString.prototype, it becomes available to all strings:undefinedundefined

    undefinedundefined

    run String.prototype.show = function() { alert(this); };

    undefinedundefined

    "BOOM!".show(); // BOOM!

    undefinedundefined

    During the process of development, we may have ideas for new built-in methods we'd like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea.

    undefinedundefined
    undefinedundefinedPrototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the method of the other.
    So, generally, modifying a native prototype is considered a bad idea.undefinedundefined
    undefinedundefined

    undefinedundefinedIn modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.undefinedundefined

    undefinedundefined

    Polyfilling is a term for making a substitute for a method that exists in the JavaScript specification, but is not yet supported by a particular JavaScript engine.

    undefinedundefined

    We may then implement it manually and populate the built-in prototype with it.

    undefinedundefined

    For instance:

    undefinedundefined

    run if (!String.prototype.repeat) { // if there's no such method // add it to the prototype

    undefinedundefined

    String.prototype.repeat = function(n) { // repeat the string n times

    undefinedundefined
    undefinedundefined// actually, the code should be a little bit more complex than that
    // (the full algorithm is in the specification)
    // but even an imperfect polyfill is often considered good enough
    return new Array(n + 1).join(this);undefinedundefined
    undefinedundefined

    }; }

    undefinedundefined

    alert( "La".repeat(3) ); // LaLaLa

    undefinedundefined

    Borrowing from prototypes

    undefinedundefined

    In the chapter undefinedundefinedinfo:call-apply-decorators#method-borrowing we talked about method borrowing.undefinedundefined

    undefinedundefined

    That's when we take a method from one object and copy it into another.

    undefinedundefined

    Some methods of native prototypes are often borrowed.

    undefinedundefined

    For instance, if we're making an array-like object, we may want to copy some undefinedundefinedArray methods to it.undefinedundefined

    undefinedundefined

    E.g.

    undefinedundefined

    run let obj = { 0: "Hello", 1: "world!", length: 2, };

    undefinedundefined

    undefinedundefined! obj.join = Array.prototype.join; undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( obj.join(‘,''') ); // Hello,world!

    undefinedundefined

    It works because the internal algorithm of the built-in undefinedundefinedjoin method only cares about the correct indexes and the undefinedundefinedlength property. It doesn't check if the object is indeed an array. Many built-in methods are like that.undefinedundefined

    undefinedundefined

    Another possibility is to inherit by setting undefinedundefinedobj.__proto__ to undefinedundefinedArray.prototype, so all undefinedundefinedArray methods are automatically available in undefinedundefinedobj.undefinedundefined

    undefinedundefined

    But that's impossible if undefinedundefinedobj already inherits from another object. Remember, we only can inherit from one object at a time.undefinedundefined

    undefinedundefined

    Borrowing methods is flexible, it allows to mix functionalities from different objects if needed.

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • All built-in objects follow the same pattern: undefinedundefined
        undefinedundefined
      • The methods are stored in the prototype (undefinedundefinedArray.prototype, undefinedundefinedObject.prototype, undefinedundefinedDate.prototype, etc.)undefinedundefined
      • undefinedundefined
      • The object itself stores only the data (array items, object properties, the date)
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Primitives also store methods in prototypes of wrapper objects: undefinedundefinedNumber.prototype, undefinedundefinedString.prototype and undefinedundefinedBoolean.prototype. Only undefinedundefinedundefined and undefinedundefinednull do not have wrapper objectsundefinedundefined
    • undefinedundefined
    • Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. The only allowable case is probably when we add-in a new standard, but it's not yet supported by the JavaScript engine
    • undefinedundefined
    undefinedundefined

    Prototype methods, objects without undefinedundefinedprotoundefinedundefined

    undefinedundefined

    In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.

    undefinedundefined

    The undefinedundefined__proto__ is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).undefinedundefined

    undefinedundefined

    The modern methods are:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedObject.create(proto, [descriptors]) - creates an empty object with given undefinedundefinedproto as undefinedundefined[[Prototype]] and optional property descriptors.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.getPrototypeOf(obj) - returns the undefinedundefined[[Prototype]] of undefinedundefinedobj.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.setPrototypeOf(obj, proto) - sets the undefinedundefined[[Prototype]] of undefinedundefinedobj to undefinedundefinedproto.undefinedundefined
    • undefinedundefined
    undefinedundefined

    These should be used instead of undefinedundefined__proto__.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let animal = { eats: true };

    undefinedundefined

    // create a new object with animal as a prototype undefinedundefined! let rabbit = Object.create(animal); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(rabbit.eats); // true

    undefinedundefined

    undefinedundefined! alert(Object.getPrototypeOf(rabbit) === animal); // true undefinedundefined/!undefinedundefined

    undefinedundefined

    undefinedundefined! Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} undefinedundefined/! undefinedundefined

    undefinedundefined

    undefinedundefinedObject.create has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:undefinedundefined

    undefinedundefined

    run let animal = { eats: true };

    undefinedundefined

    let rabbit = Object.create(animal, { jumps: { value: true } });

    undefinedundefined

    alert(rabbit.jumps); // true

    undefinedundefined

    The descriptors are in the same format as described in the chapter undefinedundefinedinfo:property-descriptors.undefinedundefined

    undefinedundefined

    We can use undefinedundefinedObject.create to perform an object cloning more powerful than copying properties in undefinedundefinedfor..in:undefinedundefined

    undefinedundefinedundefinedundefined

    This call makes a truly exact copy of undefinedundefinedobj, including all properties: enumerable and non-enumerable, data properties and setters/getters - everything, and with the right undefinedundefined[[Prototype]].undefinedundefined

    undefinedundefined

    Brief history

    undefinedundefined

    If we count all the ways to manage undefinedundefined[[Prototype]], there are a lot! Many ways to do the same thing!undefinedundefined

    undefinedundefined

    Why?

    undefinedundefined

    That's for historical reasons.

    undefinedundefined
      undefinedundefined
    • The undefinedundefined"prototype" property of a constructor function has worked since very ancient times.undefinedundefined
    • undefinedundefined
    • Later, in the year 2012, undefinedundefinedObject.create appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. So browsers implemented the non-standard undefinedundefined__proto__ accessor that allowed the user to get/set a prototype at any time.undefinedundefined
    • undefinedundefined
    • Later, in the year 2015, undefinedundefinedObject.setPrototypeOf and undefinedundefinedObject.getPrototypeOf were added to the standard, to perform the same functionality as undefinedundefined__proto__. As undefinedundefined__proto__ was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments.undefinedundefined
    • undefinedundefined
    undefinedundefined

    As of now we have all these ways at our disposal.

    undefinedundefined

    Why was undefinedundefined__proto__ replaced by the functions undefinedundefinedgetPrototypeOf/setPrototypeOf? That's an interesting question, requiring us to understand why undefinedundefined__proto__ is bad. Read on to get the answer.undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="Don't change[[Prototype]]undefinedundefinedon existing objects if speed matters" Technically, we can get/set[[Prototype]]undefinedundefinedat any time. But usually we only set it once at the object creation time and don't modify it anymore:rabbitundefinedundefinedinherits fromanimal`, and that is not going to change.undefinedundefined

    undefinedundefined

    And JavaScript engines are highly optimized for this. Changing a prototype "on-the-fly" with undefinedundefinedObject.setPrototypeOf or undefinedundefinedobj.__proto__= is a very slow operation as it breaks internal optimizations for object property access operations. So avoid it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. undefinedundefined

    undefinedundefined

    "Very plain" objects [#very-plain]

    undefinedundefined

    As we know, objects can be used as associative arrays to store key/value pairs.

    undefinedundefined

    …But if we try to store undefinedundefineduser-provided keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except undefinedundefined"__proto__".undefinedundefined

    undefinedundefined

    Check out the example:

    undefinedundefined

    run let obj = {};

    undefinedundefined

    let key = prompt("What's the key?", "undefinedundefinedproto"); obj[key] = "some value";undefinedundefined

    undefinedundefined

    alert(obj[key]); // [object Object], not "some value"!

    undefinedundefined

    Here, if the user types in undefinedundefined__proto__, the assignment is ignored!undefinedundefined

    undefinedundefined

    That shouldn't surprise us. The undefinedundefined__proto__ property is special: it must be either an object or undefinedundefinednull. A string can not become a prototype.undefinedundefined

    undefinedundefined

    But we didn't undefinedundefinedintend to implement such behavior, right? We want to store key/value pairs, and the key named undefinedundefined"__proto__" was not properly saved. So that's a bug!undefinedundefined

    undefinedundefined

    Here the consequences are not terrible. But in other cases we may be assigning object values, and then the prototype may indeed be changed. As a result, the execution will go wrong in totally unexpected ways.

    undefinedundefined

    What's worse - usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side.

    undefinedundefined

    Unexpected things also may happen when assigning to undefinedundefinedtoString, which is a function by default, and to other built-in methods.undefinedundefined

    undefinedundefined

    How can we avoid this problem?

    undefinedundefined

    First, we can just switch to using undefinedundefinedMap for storage instead of plain objects, then everything's fine.undefinedundefined

    undefinedundefined

    But undefinedundefinedObject can also serve us well here, because language creators gave thought to that problem long ago.undefinedundefined

    undefinedundefined

    undefinedundefined__proto__ is not a property of an object, but an accessor property of undefinedundefinedObject.prototype:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    So, if undefinedundefinedobj.__proto__ is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets undefinedundefined[[Prototype]].undefinedundefined

    undefinedundefined

    As it was said in the beginning of this tutorial section: undefinedundefined__proto__ is a way to access undefinedundefined[[Prototype]], it is not undefinedundefined[[Prototype]] itself.undefinedundefined

    undefinedundefined

    Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick:

    undefinedundefined

    run undefinedundefined! let obj = Object.create(null); undefinedundefined/!undefinedundefined

    undefinedundefined

    let key = prompt("What's the key?", "undefinedundefinedproto"); obj[key] = "some value";undefinedundefined

    undefinedundefined

    alert(obj[key]); // "some value"

    undefinedundefined

    undefinedundefinedObject.create(null) creates an empty object without a prototype (undefinedundefined[[Prototype]] is undefinedundefinednull):undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    So, there is no inherited getter/setter for undefinedundefined__proto__. Now it is processed as a regular data property, so the example above works right.undefinedundefined

    undefinedundefined

    We can call such objects "very plain" or "pure dictionary" objects, because they are even simpler than the regular plain object undefinedundefined{...}.undefinedundefined

    undefinedundefined

    A downside is that such objects lack any built-in object methods, e.g. undefinedundefinedtoString:undefinedundefined

    undefinedundefined

    run undefinedundefined! let obj = Object.create(null); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(obj); // Error (no toString)

    undefinedundefined

    …But that's usually fine for associative arrays.

    undefinedundefined

    Note that most object-related methods are undefinedundefinedObject.something(...), like undefinedundefinedObject.keys(obj) - they are not in the prototype, so they will keep working on such objects:undefinedundefined

    undefinedundefined

    run let chineseDictionary = Object.create(null); chineseDictionary.hello = "你好"; chineseDictionary.bye = "再见";

    undefinedundefined

    alert(Object.keys(chineseDictionary)); // hello,bye

    undefinedundefined

    Summary

    undefinedundefined

    Modern methods to set up and directly access the prototype are:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedObject.create(proto, [descriptors]) - creates an empty object with a given undefinedundefinedproto as undefinedundefined[[Prototype]] (can be undefinedundefinednull) and optional property descriptors.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.getPrototypeOf(obj) - returns the undefinedundefined[[Prototype]] of undefinedundefinedobj (same as undefinedundefined__proto__ getter).undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.setPrototypeOf(obj, proto) - sets the undefinedundefined[[Prototype]] of undefinedundefinedobj to undefinedundefinedproto (same as undefinedundefined__proto__ setter).undefinedundefined
    • undefinedundefined
    undefinedundefined

    The built-in undefinedundefined__proto__ getter/setter is unsafe if we'd want to put user-generated keys into an object. Just because a user may enter undefinedundefined"__proto__" as the key, and there'll be an error, with hopefully light, but generally unpredictable consequences.undefinedundefined

    undefinedundefined

    So we can either use undefinedundefinedObject.create(null) to create a "very plain" object without undefinedundefined__proto__, or stick to undefinedundefinedMap objects for that.undefinedundefined

    undefinedundefined

    Also, undefinedundefinedObject.create provides an easy way to shallow-copy an object with all descriptors:undefinedundefined

    undefinedundefinedundefinedundefined

    We also made it clear that undefinedundefined__proto__ is a getter/setter for undefinedundefined[[Prototype]] and resides in undefinedundefinedObject.prototype, just like other methods.undefinedundefined

    undefinedundefined

    We can create an object without a prototype by undefinedundefinedObject.create(null). Such objects are used as "pure dictionaries", they have no issues with undefinedundefined"__proto__" as the key.undefinedundefined

    undefinedundefined

    Other methods:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedObject.keys(obj) / undefinedundefinedObject.values(obj) / undefinedundefinedObject.entries(obj) - returns an array of enumerable own string property names/values/key-value pairs.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.getOwnPropertySymbols(obj) - returns an array of all own symbolic keys.undefinedundefined
    • undefinedundefined
    • undefinedundefinedObject.getOwnPropertyNames(obj) - returns an array of all own string keys.undefinedundefined
    • undefinedundefined
    • undefinedundefinedReflect.ownKeys(obj) - returns an array of all own keys.undefinedundefined
    • undefinedundefined
    • undefinedundefinedobj.hasOwnProperty(key): returns undefinedundefinedtrue if undefinedundefinedobj has its own (not inherited) key named undefinedundefinedkey.undefinedundefined
    • undefinedundefined
    undefinedundefined

    All methods that return object properties (like undefinedundefinedObject.keys and others) - return "own" properties. If we want inherited ones, we can use undefinedundefinedfor..in.undefinedundefined

    undefinedundefined

    Class basic syntax

    undefinedundefined

    undefinedundefinedquote author="Wikipedia" In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).undefinedundefined

    undefinedundefined

    In practice, we often need to create many objects of the same kind, like users, or goods or whatever.

    undefinedundefined

    As we already know from the chapter undefinedundefinedinfo:constructor-new, undefinedundefinednew function can help with that.undefinedundefined

    undefinedundefined

    But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming.

    undefinedundefined

    The "class" syntax

    undefinedundefined

    The basic syntax is:

    undefinedundefinedundefinedundefined

    Then use undefinedundefinednew MyClass() to create a new object with all the listed methods.undefinedundefined

    undefinedundefined

    The undefinedundefinedconstructor() method is called automatically by undefinedundefinednew, so we can initialize the object there.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefined

    run class User {

    undefinedundefined

    constructor(name) { this.name = name; }

    undefinedundefined

    sayHi() { alert(this.name); }

    undefinedundefined

    }

    undefinedundefined

    // Usage: let user = new User("John"); user.sayHi();

    undefinedundefined

    When undefinedundefinednew User("John") is called: 1. A new object is created. 2. The undefinedundefinedconstructor runs with the given argument and assigns it to undefinedundefinedthis.name.undefinedundefined

    undefinedundefined

    …Then we can call object methods, such as undefinedundefineduser.sayHi().undefinedundefined

    undefinedundefined

    warn header="No comma between class methods" A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error.

    undefinedundefined

    The notation here is not to be confused with object literals. Within the class, no commas are required.

    undefinedundefined

    What is a class?

    undefinedundefined

    So, what exactly is a undefinedundefinedclass? That's not an entirely new language-level entity, as one might think.undefinedundefined

    undefinedundefined

    Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects.

    undefinedundefined

    In JavaScript, a class is a kind of function.

    undefinedundefined

    Here, take a look:

    undefinedundefined

    run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } }

    undefinedundefined

    // proof: User is a function undefinedundefined! alert(typeof User); // function undefinedundefined/! undefinedundefined

    undefinedundefined

    What undefinedundefinedclass User {...} construct really does is:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Creates a function named undefinedundefinedUser, that becomes the result of the class declaration. The function code is taken from the undefinedundefinedconstructor method (assumed empty if we don't write such method).undefinedundefined
    2. undefinedundefined
    3. Stores class methods, such as undefinedundefinedsayHi, in undefinedundefinedUser.prototype.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    After undefinedundefinednew User object is created, when we call its method, it's taken from the prototype, just as described in the chapter undefinedundefinedinfo:function-prototype. So the object has access to class methods.undefinedundefined

    undefinedundefined

    We can illustrate the result of undefinedundefinedclass User declaration as:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Here's the code to introspect it:

    undefinedundefined

    run class User { constructor(name) { this.name = name; } sayHi() { alert(this.name); } }

    undefinedundefined

    // class is a function alert(typeof User); // function

    undefinedundefined

    // …or, more precisely, the constructor method alert(User === User.prototype.constructor); // true

    undefinedundefined

    // The methods are in User.prototype, e.g: alert(User.prototype.sayHi); // the code of the sayHi method

    undefinedundefined

    // there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

    undefinedundefined

    Not just a syntactic sugar

    undefinedundefined

    Sometimes people say that undefinedundefinedclass is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without undefinedundefinedclass keyword at all:undefinedundefined

    undefinedundefined

    run // rewriting class User in pure functions

    undefinedundefined

    // 1. Create constructor function function User(name) { this.name = name; } // a function prototype has "constructor" property by default, // so we don't need to create it

    undefinedundefined

    // 2. Add the method to prototype User.prototype.sayHi = function() { alert(this.name); };

    undefinedundefined

    // Usage: let user = new User("John"); user.sayHi();

    undefinedundefined

    The result of this definition is about the same. So, there are indeed reasons why undefinedundefinedclass can be considered a syntactic sugar to define a constructor together with its prototype methods.undefinedundefined

    undefinedundefined

    Still, there are important differences.

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      First, a function created by undefinedundefinedclass is labelled by a special internal property undefinedundefined[[FunctionKind]]:"classConstructor". So it's not entirely the same as creating it manually.undefinedundefined

      undefinedundefined

      The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with undefinedundefinednew:undefinedundefined

      undefinedundefined

      run class User { constructor() {} }

      undefinedundefined

      alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without ‘new'

      undefinedundefined

      Also, a string representation of a class constructor in most JavaScript engines starts with the "class…"

      undefinedundefined

      run class User { constructor() {} }

      undefinedundefined

      alert(User); // class User { … } There are other differences, we'll see them soon.

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Class methods are non-enumerable. A class definition sets undefinedundefinedenumerable flag to undefinedundefinedfalse for all methods in the undefinedundefined"prototype".undefinedundefined

      undefinedundefined

      That's good, because if we undefinedundefinedfor..in over an object, we usually don't want its class methods.undefinedundefined

      undefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      Classes always undefinedundefineduse strict. All code inside the class construct is automatically in strict mode.undefinedundefined

      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Besides, undefinedundefinedclass syntax brings many other features that we'll explore later.undefinedundefined

    undefinedundefined

    Class Expression

    undefinedundefined

    Just like functions, classes can be defined inside another expression, passed around, returned, assigned, etc.

    undefinedundefined

    Here's an example of a class expression:

    undefinedundefinedundefinedundefined

    Similar to Named Function Expressions, class expressions may have a name.

    undefinedundefined

    If a class expression has a name, it's visible inside the class only:

    undefinedundefined

    run // "Named Class Expression" // (no such term in the spec, but that's similar to Named Function Expression) let User = class undefinedundefined!MyClassundefinedundefined/! { sayHi() { alert(MyClass); // MyClass name is visible only inside the class } };undefinedundefined

    undefinedundefined

    new User().sayHi(); // works, shows MyClass definition

    undefinedundefined

    alert(MyClass); // error, MyClass name isn't visible outside of the class

    undefinedundefined

    We can even make classes dynamically "on-demand", like this:

    undefinedundefined

    run function makeClass(phrase) { // declare a class and return it return class { sayHi() { alert(phrase); } }; }

    undefinedundefined

    // Create a new class let User = makeClass("Hello");

    undefinedundefined

    new User().sayHi(); // Hello

    undefinedundefined

    Getters/setters

    undefinedundefined

    Just like literal objects, classes may include getters/setters, computed properties etc.

    undefinedundefined

    Here's an example for undefinedundefineduser.name implemented using undefinedundefinedget/set:undefinedundefined

    undefinedundefined

    run class User {

    undefinedundefined

    constructor(name) { // invokes the setter this.name = name; }

    undefinedundefined

    undefinedundefined! get name() { undefinedundefined/! return this._name; }undefinedundefined

    undefinedundefined

    undefinedundefined! set name(value) { undefinedundefined/! if (value.length < 4) { alert("Name is too short."); return; } this._name = value; }undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    let user = new User("John"); alert(user.name); // John

    undefinedundefined

    user = new User(""); // Name is too short.

    undefinedundefined

    Technically, such class declaration works by creating getters and setters in undefinedundefinedUser.prototype.undefinedundefined

    undefinedundefined

    Computed names […]

    undefinedundefined

    Here's an example with a computed method name using brackets undefinedundefined[...]:undefinedundefined

    undefinedundefined

    run class User {

    undefinedundefined

    undefinedundefined!undefinedundefined‘say' + ‘Hi' { undefinedundefined/! alert("Hello"); }undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    new User().sayHi();

    undefinedundefined

    Such features are easy to remember, as they resemble that of literal objects.

    undefinedundefined

    Class fields

    undefinedundefined

    undefinedundefinedwarn header="Old browsers may need a polyfill" Class fields are a recent addition to the language.undefinedundefined

    undefinedundefined

    Previously, our classes only had methods.

    undefinedundefined

    "Class fields" is a syntax that allows to add any properties.

    undefinedundefined

    For instance, let's add undefinedundefinedname property to undefinedundefinedclass User:undefinedundefined

    undefinedundefined

    run class User { undefinedundefined! name = "John"; undefinedundefined/!undefinedundefined

    undefinedundefined

    sayHi() { alert(undefinedundefinedHello, ${this.name}!); } }undefinedundefined

    undefinedundefined

    new User().sayHi(); // Hello, John!

    undefinedundefined

    So, we just write "undefinedundefined = undefinedundefined" in the declaration, and that's it.undefinedundefined

    undefinedundefined

    The important difference of class fields is that they are set on individual objects, not undefinedundefinedUser.prototype:undefinedundefined

    undefinedundefined

    run class User { undefinedundefined! name = "John"; undefinedundefined/! }undefinedundefined

    undefinedundefined

    let user = new User(); alert(user.name); // John alert(User.prototype.name); // undefined

    undefinedundefined

    We can also assign values using more complex expressions and function calls:

    undefinedundefined

    run class User { undefinedundefined! name = prompt("Name, please?", "John"); undefinedundefined/! }undefinedundefined

    undefinedundefined

    let user = new User(); alert(user.name); // John

    undefinedundefined

    Making bound methods with class fields

    undefinedundefined

    As demonstrated in the chapter undefinedundefinedinfo:bind functions in JavaScript have a dynamic undefinedundefinedthis. It depends on the context of the call.undefinedundefined

    undefinedundefined

    So if an object method is passed around and called in another context, undefinedundefinedthis won't be a reference to its object any more.undefinedundefined

    undefinedundefined

    For instance, this code will show undefinedundefinedundefined:undefinedundefined

    undefinedundefined

    run class Button { constructor(value) { this.value = value; }

    undefinedundefined

    click() { alert(this.value); } }

    undefinedundefined

    let button = new Button("hello");

    undefinedundefined

    undefinedundefined! setTimeout(button.click, 1000); // undefined undefinedundefined/! undefinedundefined

    undefinedundefined

    The problem is called "losing undefinedundefinedthis".undefinedundefined

    undefinedundefined

    There are two approaches to fixing it, as discussed in the chapter undefinedundefinedinfo:bind:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Pass a wrapper-function, such as undefinedundefinedsetTimeout(() => button.click(), 1000).undefinedundefined
    2. undefinedundefined
    3. Bind the method to object, e.g. in the constructor.
    4. undefinedundefined
    undefinedundefined

    Class fields provide another, quite elegant syntax:

    undefinedundefined

    run class Button { constructor(value) { this.value = value; } undefinedundefined! click = () => { alert(this.value); } undefinedundefined/! }undefinedundefined

    undefinedundefined

    let button = new Button("hello");

    undefinedundefined

    setTimeout(button.click, 1000); // hello

    undefinedundefined

    The class field undefinedundefinedclick = () => {...} is created on a per-object basis, there's a separate function for each undefinedundefinedButton object, with undefinedundefinedthis inside it referencing that object. We can pass undefinedundefinedbutton.click around anywhere, and the value of undefinedundefinedthis will always be correct.undefinedundefined

    undefinedundefined

    That's especially useful in browser environment, for event listeners.

    undefinedundefined

    Summary

    undefinedundefined

    The basic class syntax looks like this:

    undefinedundefinedundefinedundefined

    undefinedundefinedMyClass is technically a function (the one that we provide as undefinedundefinedconstructor), while methods, getters and setters are written to undefinedundefinedMyClass.prototype.undefinedundefined

    undefinedundefined

    In the next chapters we'll learn more about classes, including inheritance and other features.

    undefinedundefined

    Class inheritance

    undefinedundefined

    Class inheritance is a way for one class to extend another class.

    undefinedundefined

    So we can create new functionality on top of the existing.

    undefinedundefined

    The "extends" keyword

    undefinedundefined

    Let's say we have class undefinedundefinedAnimal:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedclass Animal undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedconstructor(name) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedthis.undefinedundefinedspeedundefinedundefined=undefinedundefined0undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedthis.undefinedundefinednameundefinedundefined= nameundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedrun(speed) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedthis.undefinedundefinedspeedundefinedundefined= speedundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedalert(undefinedundefined`undefinedundefined${undefinedundefinedthis.undefinedundefinednameundefinedundefined}undefinedundefined runs with speed undefinedundefined${undefinedundefinedthis.undefinedundefinedspeedundefinedundefined}undefinedundefined.`)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedstop() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedthis.undefinedundefinedspeedundefinedundefined=undefinedundefined0undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedalert(undefinedundefined`undefinedundefined${undefinedundefinedthis.undefinedundefinednameundefinedundefined}undefinedundefined stands still.`)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedlet animal undefinedundefined=undefinedundefinednewundefinedundefinedAnimal(undefinedundefined"My animal")undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Here's how we can represent undefinedundefinedanimal object and undefinedundefinedAnimal class graphically:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    …And we would like to create another undefinedundefinedclass Rabbit.undefinedundefined

    undefinedundefined

    As rabbits are animals, undefinedundefinedRabbit class should be based on undefinedundefinedAnimal, have access to animal methods, so that rabbits can do what "generic" animals can do.undefinedundefined

    undefinedundefined

    The syntax to extend another class is: undefinedundefinedclass Child extends Parent.undefinedundefined

    undefinedundefined

    Let's create undefinedundefinedclass Rabbit that inherits from undefinedundefinedAnimal:undefinedundefined

    undefinedundefinedundefinedundefined

    Object of undefinedundefinedRabbit class have access both to undefinedundefinedRabbit methods, such as undefinedundefinedrabbit.hide(), and also to undefinedundefinedAnimal methods, such as undefinedundefinedrabbit.run().undefinedundefined

    undefinedundefined

    Internally, undefinedundefinedextends keyword works using the good old prototype mechanics. It sets undefinedundefinedRabbit.prototype.[[Prototype]] to undefinedundefinedAnimal.prototype. So, if a method is not found in undefinedundefinedRabbit.prototype, JavaScript takes it from undefinedundefinedAnimal.prototype.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    For instance, to find undefinedundefinedrabbit.run method, the engine checks (bottom-up on the picture): 1. The undefinedundefinedrabbit object (has no undefinedundefinedrun). 2. Its prototype, that is undefinedundefinedRabbit.prototype (has undefinedundefinedhide, but not undefinedundefinedrun). 3. Its prototype, that is (due to undefinedundefinedextends) undefinedundefinedAnimal.prototype, that finally has the undefinedundefinedrun method.undefinedundefined

    undefinedundefined

    As we can recall from the chapter undefinedundefinedinfo:native-prototypes, JavaScript itself uses prototypal inheritance for built-in objects. E.g. undefinedundefinedDate.prototype.[[Prototype]] is undefinedundefinedObject.prototype. That's why dates have access to generic object methods.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Any expression is allowed afterextendsundefinedundefined" Class syntax allows to specify not just a class, but any expression afterextends`.undefinedundefined

    undefinedundefined

    For instance, a function call that generates the parent class:

    undefinedundefined

    run function f(phrase) { return class { sayHi() { alert(phrase); } }; }

    undefinedundefined

    undefinedundefined! class User extends f("Hello") {} undefinedundefined/!undefinedundefined

    undefinedundefined

    new User().sayHi(); // Hello

    undefinedundefined
    undefinedundefinedHere `class User` inherits from the result of `f("Hello")`.
    That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them.undefinedundefined
    undefinedundefined

    Overriding a method

    undefinedundefined

    Now let's move forward and override a method. By default, all methods that are not specified in undefinedundefinedclass Rabbit are taken directly "as is" from undefinedundefinedclass Animal.undefinedundefined

    undefinedundefined

    But if we specify our own method in undefinedundefinedRabbit, such as undefinedundefinedstop() then it will be used instead:undefinedundefined

    undefinedundefinedundefinedundefined

    Usually we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.

    undefinedundefined

    Classes provide undefinedundefined"super" keyword for that.undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedsuper.method(...) to call a parent method.undefinedundefined
    • undefinedundefined
    • undefinedundefinedsuper(...) to call a parent constructor (inside our constructor only).undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance, let our rabbit autohide when stopped:

    undefinedundefined

    run class Animal {

    undefinedundefined

    constructor(name) { this.speed = 0; this.name = name; }

    undefinedundefined

    run(speed) { this.speed = speed; alert(undefinedundefined${this.name} runs with speed ${this.speed}.); }undefinedundefined

    undefinedundefined

    stop() { this.speed = 0; alert(undefinedundefined${this.name} stands still.); }undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    class Rabbit extends Animal { hide() { alert(undefinedundefined${this.name} hides!); }undefinedundefined

    undefinedundefined

    undefinedundefined! stop() { super.stop(); // call parent stop this.hide(); // and then hide } undefinedundefined/! }undefinedundefined

    undefinedundefined

    let rabbit = new Rabbit("White Rabbit");

    undefinedundefined

    rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stands still. White Rabbit hides!

    undefinedundefined

    Now undefinedundefinedRabbit has the undefinedundefinedstop method that calls the parent undefinedundefinedsuper.stop() in the process.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Arrow functions have nosuperundefinedundefined" As was mentioned in the chapter <info:arrow-functions>, arrow functions do not havesuper`.undefinedundefined

    undefinedundefined

    If accessed, it's taken from the outer function. For instance:

    undefinedundefinedundefinedundefined

    The undefinedundefinedsuper in the arrow function is the same as in undefinedundefinedstop(), so it works as intended. If we specified a "regular" function here, there would be an error:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Overriding constructor

    undefinedundefined

    With constructors it gets a little bit tricky.

    undefinedundefined

    Until now, undefinedundefinedRabbit did not have its own undefinedundefinedconstructor.undefinedundefined

    undefinedundefined

    According to the undefinedundefinedspecification, if a class extends another class and has no undefinedundefinedconstructor, then the following "empty" undefinedundefinedconstructor is generated:undefinedundefined

    undefinedundefinedundefinedundefined

    As we can see, it basically calls the parent undefinedundefinedconstructor passing it all the arguments. That happens if we don't write a constructor of our own.undefinedundefined

    undefinedundefined

    Now let's add a custom constructor to undefinedundefinedRabbit. It will specify the undefinedundefinedearLength in addition to undefinedundefinedname:undefinedundefined

    undefinedundefined

    run class Animal { constructor(name) { this.speed = 0; this.name = name; } // … }

    undefinedundefined

    class Rabbit extends Animal {

    undefinedundefined

    undefinedundefined! constructor(name, earLength) { this.speed = 0; this.name = name; this.earLength = earLength; } undefinedundefined/!undefinedundefined

    undefinedundefined

    // … }

    undefinedundefined

    undefinedundefined! // Doesn't work! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. undefinedundefined/! undefinedundefined

    undefinedundefined

    Whoops! We've got an error. Now we can't create rabbits. What went wrong?

    undefinedundefined

    The short answer is:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedConstructors in inheriting classes must call undefinedundefinedsuper(...), and (!) do it before using undefinedundefinedthis.undefinedundefinedundefinedundefined
    • undefinedundefined
    undefinedundefined

    …But why? What's going on here? Indeed, the requirement seems strange.

    undefinedundefined

    Of course, there's an explanation. Let's get into details, so you'll really understand what's going on.

    undefinedundefined

    In JavaScript, there's a distinction between a constructor function of an inheriting class (so-called "derived constructor") and other functions. A derived constructor has a special internal property undefinedundefined[[ConstructorKind]]:"derived". That's a special internal label.undefinedundefined

    undefinedundefined

    That label affects its behavior with undefinedundefinednew.undefinedundefined

    undefinedundefined
      undefinedundefined
    • When a regular function is executed with undefinedundefinednew, it creates an empty object and assigns it to undefinedundefinedthis.undefinedundefined
    • undefinedundefined
    • But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job.
    • undefinedundefined
    undefinedundefined

    So a derived constructor must call undefinedundefinedsuper in order to execute its parent (base) constructor, otherwise the object for undefinedundefinedthis won't be created. And we'll get an error.undefinedundefined

    undefinedundefined

    For the undefinedundefinedRabbit constructor to work, it needs to call undefinedundefinedsuper() before using undefinedundefinedthis, like here:undefinedundefined

    undefinedundefined

    run class Animal {

    undefinedundefined

    constructor(name) { this.speed = 0; this.name = name; }

    undefinedundefined

    // … }

    undefinedundefined

    class Rabbit extends Animal {

    undefinedundefined

    constructor(name, earLength) { undefinedundefined! super(name); undefinedundefined/! this.earLength = earLength; }undefinedundefined

    undefinedundefined

    // … }

    undefinedundefined

    undefinedundefined! // now fine let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10 undefinedundefined/! undefinedundefined

    undefinedundefined

    Overriding class fields: a tricky note

    undefinedundefined

    warn header="Advanced note" This note assumes you have a certain experience with classes, maybe in other programming languages.

    undefinedundefined

    It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often).

    undefinedundefined

    If you find it difficult to understand, just go on, continue reading, then return to it some time later.

    undefinedundefined

    We can override not only methods, but also class fields.

    undefinedundefined

    Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages.

    undefinedundefined

    Consider this example:

    undefinedundefined

    run class Animal { name = ‘animal';

    undefinedundefined

    constructor() { alert(this.name); // (*) } }

    undefinedundefined

    class Rabbit extends Animal { name = ‘rabbit'; }

    undefinedundefined

    new Animal(); // animal undefinedundefined! new Rabbit(); // animal undefinedundefined/! undefinedundefined

    undefinedundefined

    Here, class undefinedundefinedRabbit extends undefinedundefinedAnimal and overrides undefinedundefinedname field with its own value.undefinedundefined

    undefinedundefined

    There's no own constructor in undefinedundefinedRabbit, so undefinedundefinedAnimal constructor is called.undefinedundefined

    undefinedundefined

    What's interesting is that in both cases: undefinedundefinednew Animal() and undefinedundefinednew Rabbit(), the undefinedundefinedalert in the line undefinedundefined(*) shows undefinedundefinedanimal.undefinedundefined

    undefinedundefined

    undefinedundefinedIn other words, parent constructor always uses its own field value, not the overridden one.undefinedundefined

    undefinedundefined

    What's odd about it?

    undefinedundefined

    If it's not clear yet, please compare with methods.

    undefinedundefined

    Here's the same code, but instead of undefinedundefinedthis.name field we call undefinedundefinedthis.showName() method:undefinedundefined

    undefinedundefined

    run class Animal { showName() { // instead of this.name = ‘animal' alert(‘animal'); }

    undefinedundefined

    constructor() { this.showName(); // instead of alert(this.name); } }

    undefinedundefined

    class Rabbit extends Animal { showName() { alert(‘rabbit'); } }

    undefinedundefined

    new Animal(); // animal undefinedundefined! new Rabbit(); // rabbit undefinedundefined/! undefinedundefined

    undefinedundefined

    Please note: now the output is different.

    undefinedundefined

    And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method.

    undefinedundefined

    …But for class fields it's not so. As said, the parent constructor always uses the parent field.

    undefinedundefined

    Why is there the difference?

    undefinedundefined

    Well, the reason is in the field initialization order. The class field is initialized: - Before constructor for the base class (that doesn't extend anything), - Immediately after undefinedundefinedsuper() for the derived class.undefinedundefined

    undefinedundefined

    In our case, undefinedundefinedRabbit is the derived class. There's no undefinedundefinedconstructor() in it. As said previously, that's the same as if there was an empty constructor with only undefinedundefinedsuper(...args).undefinedundefined

    undefinedundefined

    So, undefinedundefinednew Rabbit() calls undefinedundefinedsuper(), thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no undefinedundefinedRabbit class fields yet, that's why undefinedundefinedAnimal fields are used.undefinedundefined

    undefinedundefined

    This subtle difference between fields and methods is specific to JavaScript

    undefinedundefined

    Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here.

    undefinedundefined

    If it becomes a problem, one can fix it by using methods or getters/setters instead of fields.

    undefinedundefined

    Super: internals, [[HomeObject]]

    undefinedundefined

    warn header="Advanced information" If you're reading the tutorial for the first time - this section may be skipped.

    undefinedundefined

    It's about the internal mechanisms behind inheritance and undefinedundefinedsuper. undefinedundefined

    undefinedundefined

    Let's get a little deeper under the hood of undefinedundefinedsuper. We'll see some interesting things along the way.undefinedundefined

    undefinedundefined

    First to say, from all that we've learned till now, it's impossible for undefinedundefinedsuper to work at all!undefinedundefined

    undefinedundefined

    Yeah, indeed, let's ask ourselves, how it should technically work? When an object method runs, it gets the current object as undefinedundefinedthis. If we call undefinedundefinedsuper.method() then, the engine needs to get the undefinedundefinedmethod from the prototype of the current object. But how?undefinedundefined

    undefinedundefined

    The task may seem simple, but it isn't. The engine knows the current object undefinedundefinedthis, so it could get the parent undefinedundefinedmethod as undefinedundefinedthis.__proto__.method. Unfortunately, such a "naive" solution won't work.undefinedundefined

    undefinedundefined

    Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.

    undefinedundefined

    You may skip this part and go below to the undefinedundefined[[HomeObject]] subsection if you don't want to know the details. That won't harm. Or read on if you're interested in understanding things in-depth.undefinedundefined

    undefinedundefined

    In the example below, undefinedundefinedrabbit.__proto__ = animal. Now let's try: in undefinedundefinedrabbit.eat() we'll call undefinedundefinedanimal.eat(), using undefinedundefinedthis.__proto__:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let animal = { name: "Animal", eat() { alert(${this.name} eats.`); } };undefinedundefined

    undefinedundefined

    let rabbit = { undefinedundefinedproto: animal, name: "Rabbit", eat() { undefinedundefined! // that's how super.eat() could presumably work this.__proto__.eat.call(this); // (undefinedundefined) /!* } };undefinedundefined

    undefinedundefined

    rabbit.eat(); // Rabbit eats.

    undefinedundefined

    At the line undefinedundefined(*) we take undefinedundefinedeat from the prototype (undefinedundefinedanimal) and call it in the context of the current object. Please note that undefinedundefined.call(this) is important here, because a simple undefinedundefinedthis.__proto__.eat() would execute parent undefinedundefinedeat in the context of the prototype, not the current object.undefinedundefined

    undefinedundefined

    And in the code above it actually works as intended: we have the correct undefinedundefinedalert.undefinedundefined

    undefinedundefined

    Now let's add one more object to the chain. We'll see how things break:

    undefinedundefined

    ``undefinedundefinedjs run let animal = { name: "Animal", eat() { alert(${this.name} eats.`); } };undefinedundefined

    undefinedundefined

    let rabbit = { undefinedundefinedproto: animal, eat() { // …bounce around rabbit-style and call parent (animal) method this.__proto__.eat.call(this); // (*) } };undefinedundefined

    undefinedundefined

    let longEar = { undefinedundefinedproto: rabbit, eat() { // …do something with long ears and call parent (rabbit) method this.__proto__.eat.call(this); // (**) } };undefinedundefined

    undefinedundefined

    undefinedundefined! longEar.eat(); // Error: Maximum call stack size exceeded undefinedundefined/! undefinedundefined

    undefinedundefined

    The code doesn't work anymore! We can see the error trying to call undefinedundefinedlongEar.eat().undefinedundefined

    undefinedundefined

    It may be not that obvious, but if we trace undefinedundefinedlongEar.eat() call, then we can see why. In both lines undefinedundefined(*) and undefinedundefined(**) the value of undefinedundefinedthis is the current object (undefinedundefinedlongEar). That's essential: all object methods get the current object as undefinedundefinedthis, not a prototype or something.undefinedundefined

    undefinedundefined

    So, in both lines undefinedundefined(*) and undefinedundefined(**) the value of undefinedundefinedthis.__proto__ is exactly the same: undefinedundefinedrabbit. They both call undefinedundefinedrabbit.eat without going up the chain in the endless loop.undefinedundefined

    undefinedundefined

    Here's the picture of what happens:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined
      undefinedundefined
    1. Inside undefinedundefinedlongEar.eat(), the line undefinedundefined(**) calls undefinedundefinedrabbit.eat providing it with undefinedundefinedthis=longEar. undefinedundefinedjs // inside longEar.eat() we have this = longEar this.__proto__.eat.call(this) // (**) // becomes longEar.__proto__.eat.call(this) // that is rabbit.eat.call(this);undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Then in the line undefinedundefined(*) of undefinedundefinedrabbit.eat, we'd like to pass the call even higher in the chain, but undefinedundefinedthis=longEar, so undefinedundefinedthis.__proto__.eat is again undefinedundefinedrabbit.eat!undefinedundefined

      undefinedundefinedundefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      …So undefinedundefinedrabbit.eat calls itself in the endless loop, because it can't ascend any further.undefinedundefined

      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    The problem can't be solved by using undefinedundefinedthis alone.undefinedundefined

    undefinedundefined

    undefinedundefined[[HomeObject]]undefinedundefined

    undefinedundefined

    To provide the solution, JavaScript adds one more special internal property for functions: undefinedundefined[[HomeObject]].undefinedundefined

    undefinedundefined

    When a function is specified as a class or object method, its undefinedundefined[[HomeObject]] property becomes that object.undefinedundefined

    undefinedundefined

    Then undefinedundefinedsuper uses it to resolve the parent prototype and its methods.undefinedundefined

    undefinedundefined

    Let's see how it works, first with plain objects:

    undefinedundefined

    ``undefinedundefinedjs run let animal = { name: "Animal", eat() { // animal.eat.[[HomeObject]] == animal alert(${this.name} eats.`); } };undefinedundefined

    undefinedundefined

    let rabbit = { undefinedundefinedproto: animal, name: "Rabbit", eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } };undefinedundefined

    undefinedundefined

    let longEar = { undefinedundefinedproto: rabbit, name: "Long Ear", eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } };undefinedundefined

    undefinedundefined

    undefinedundefined! // works correctly longEar.eat(); // Long Ear eats. undefinedundefined/! undefinedundefined

    undefinedundefined

    It works as intended, due to undefinedundefined[[HomeObject]] mechanics. A method, such as undefinedundefinedlongEar.eat, knows its undefinedundefined[[HomeObject]] and takes the parent method from its prototype. Without any use of undefinedundefinedthis.undefinedundefined

    undefinedundefined

    Methods are not "free"

    undefinedundefined

    As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another undefinedundefinedthis.undefinedundefined

    undefinedundefined

    The very existence of undefinedundefined[[HomeObject]] violates that principle, because methods remember their objects. undefinedundefined[[HomeObject]] can't be changed, so this bond is forever.undefinedundefined

    undefinedundefined

    The only place in the language where undefinedundefined[[HomeObject]] is used - is undefinedundefinedsuper. So, if a method does not use undefinedundefinedsuper, then we can still consider it free and copy between objects. But with undefinedundefinedsuper things may go wrong.undefinedundefined

    undefinedundefined

    Here's the demo of a wrong undefinedundefinedsuper result after copying:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run let animal = { sayHi() { alert(I'm an animal`); } };undefinedundefined

    undefinedundefined

    // rabbit inherits from animal let rabbit = { undefinedundefinedproto: animal, sayHi() { super.sayHi(); } };undefinedundefined

    undefinedundefined

    let plant = { sayHi() { alert("I'm a plant"); } };

    undefinedundefined

    // tree inherits from plant let tree = { undefinedundefinedproto: plant, undefinedundefined! sayHi: rabbit.sayHi // (undefinedundefined) /!* };undefinedundefined

    undefinedundefined

    undefinedundefined! tree.sayHi(); // I'm an animal (?!?) undefinedundefined/! undefinedundefined

    undefinedundefined

    A call to undefinedundefinedtree.sayHi() shows "I'm an animal". Definitely wrong.undefinedundefined

    undefinedundefined

    The reason is simple: - In the line undefinedundefined(*), the method undefinedundefinedtree.sayHi was copied from undefinedundefinedrabbit. Maybe we just wanted to avoid code duplication? - Its undefinedundefined[[HomeObject]] is undefinedundefinedrabbit, as it was created in undefinedundefinedrabbit. There's no way to change undefinedundefined[[HomeObject]]. - The code of undefinedundefinedtree.sayHi() has undefinedundefinedsuper.sayHi() inside. It goes up from undefinedundefinedrabbit and takes the method from undefinedundefinedanimal.undefinedundefined

    undefinedundefined

    Here's the diagram of what happens:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Methods, not function properties

    undefinedundefined

    undefinedundefined[[HomeObject]] is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as undefinedundefinedmethod(), not as undefinedundefined"method: function()".undefinedundefined

    undefinedundefined

    The difference may be non-essential for us, but it's important for JavaScript.

    undefinedundefined

    In the example below a non-method syntax is used for comparison. undefinedundefined[[HomeObject]] property is not set and the inheritance doesn't work:undefinedundefined

    undefinedundefined

    run let animal = { eat: function() { // intentionally writing like this instead of eat() {… // … } };

    undefinedundefined

    let rabbit = { undefinedundefinedproto: animal, eat: function() { super.eat(); } };undefinedundefined

    undefinedundefined

    undefinedundefined! rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) undefinedundefined/! undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    1. To extend a class: undefinedundefinedclass Child extends Parent: undefinedundefined
        undefinedundefined
      • That means undefinedundefinedChild.prototype.__proto__ will be undefinedundefinedParent.prototype, so methods are inherited.undefinedundefined
      • undefinedundefined
      undefinedundefined
    2. undefinedundefined
    3. When overriding a constructor: undefinedundefined
        undefinedundefined
      • We must call parent constructor as undefinedundefinedsuper() in undefinedundefinedChild constructor before using undefinedundefinedthis.undefinedundefined
      • undefinedundefined
      undefinedundefined
    4. undefinedundefined
    5. When overriding another method: undefinedundefined
        undefinedundefined
      • We can use undefinedundefinedsuper.method() in a undefinedundefinedChild method to call undefinedundefinedParent method.undefinedundefined
      • undefinedundefined
      undefinedundefined
    6. undefinedundefined
    7. Internals: undefinedundefined
        undefinedundefined
      • Methods remember their class/object in the internal undefinedundefined[[HomeObject]] property. That's how undefinedundefinedsuper resolves parent methods.undefinedundefined
      • undefinedundefined
      • So it's not safe to copy a method with undefinedundefinedsuper from one object to another.undefinedundefined
      • undefinedundefined
      undefinedundefined
    8. undefinedundefined
    undefinedundefined

    Also: - Arrow functions don't have their own undefinedundefinedthis or undefinedundefinedsuper, so they transparently fit into the surrounding context.undefinedundefined

    undefinedundefined

    Static properties and methods

    undefinedundefined

    We can also assign a method to the class function itself, not to its undefinedundefined"prototype". Such methods are called undefinedundefinedstatic.undefinedundefined

    undefinedundefined

    In a class, they are prepended by undefinedundefinedstatic keyword, like this:undefinedundefined

    undefinedundefined

    run class User { undefinedundefined! static staticMethod() { undefinedundefined/! alert(this === User); } }undefinedundefined

    undefinedundefined

    User.staticMethod(); // true

    undefinedundefined

    That actually does the same as assigning it as a property directly:

    undefinedundefined

    run class User { }

    undefinedundefined

    User.staticMethod = function() { alert(this === User); };

    undefinedundefined

    User.staticMethod(); // true

    undefinedundefined

    The value of undefinedundefinedthis in undefinedundefinedUser.staticMethod() call is the class constructor undefinedundefinedUser itself (the "object before dot" rule).undefinedundefined

    undefinedundefined

    Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.

    undefinedundefined

    For instance, we have undefinedundefinedArticle objects and need a function to compare them. A natural solution would be to add undefinedundefinedArticle.compare method, like this:undefinedundefined

    undefinedundefined

    run class Article { constructor(title, date) { this.title = title; this.date = date; }

    undefinedundefined

    undefinedundefined! static compare(articleA, articleB) { return articleA.date - articleB.date; } undefinedundefined/! }undefinedundefined

    undefinedundefined

    // usage let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1))];

    undefinedundefined

    undefinedundefined! articles.sort(Article.compare); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( articles[0].title ); // CSS

    undefinedundefined

    Here undefinedundefinedArticle.compare stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.undefinedundefined

    undefinedundefined

    Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:

    undefinedundefined
      undefinedundefined
    1. Create by given parameters (undefinedundefinedtitle, undefinedundefineddate etc).undefinedundefined
    2. undefinedundefined
    3. Create an empty article with today's date.
    4. undefinedundefined
    5. …or else somehow.
    6. undefinedundefined
    undefinedundefined

    The first way can be implemented by the constructor. And for the second one we can make a static method of the class.

    undefinedundefined

    Like undefinedundefinedArticle.createTodays() here:undefinedundefined

    undefinedundefined

    run class Article { constructor(title, date) { this.title = title; this.date = date; }

    undefinedundefined

    undefinedundefined! static createTodays() { // remember, this = Article return new this("Today's digest", new Date()); } undefinedundefined/! }undefinedundefined

    undefinedundefined

    let article = Article.createTodays();

    undefinedundefined

    alert( article.title ); // Today's digest

    undefinedundefined

    Now every time we need to create a today's digest, we can call undefinedundefinedArticle.createTodays(). Once again, that's not a method of an article, but a method of the whole class.undefinedundefined

    undefinedundefined

    Static methods are also used in database-related classes to search/save/remove entries from the database, like this:

    undefinedundefinedundefinedundefined

    Static properties

    undefinedundefined

    [recent browser=Chrome]

    undefinedundefined

    Static properties are also possible, they look like regular class properties, but prepended by undefinedundefinedstatic:undefinedundefined

    undefinedundefined

    run class Article { static publisher = "Ilya Kantor"; }

    undefinedundefined

    alert( Article.publisher ); // Ilya Kantor

    undefinedundefined

    That is the same as a direct assignment to undefinedundefinedArticle:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedArticle.undefinedundefinedpublisherundefinedundefined=undefinedundefined"Ilya Kantor"undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Inheritance of static properties and methods [#statics-and-inheritance]

    undefinedundefined

    Static properties and methods are inherited.

    undefinedundefined

    For instance, undefinedundefinedAnimal.compare and undefinedundefinedAnimal.planet in the code below are inherited and accessible as undefinedundefinedRabbit.compare and undefinedundefinedRabbit.planet:undefinedundefined

    undefinedundefined

    run class Animal { static planet = "Earth";

    undefinedundefined

    constructor(name, speed) { this.speed = speed; this.name = name; }

    undefinedundefined

    run(speed = 0) { this.speed += speed; alert(undefinedundefined${this.name} runs with speed ${this.speed}.); }undefinedundefined

    undefinedundefined

    undefinedundefined! static compare(animalA, animalB) { return animalA.speed - animalB.speed; } undefinedundefined/!undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    // Inherit from Animal class Rabbit extends Animal { hide() { alert(undefinedundefined${this.name} hides!); } }undefinedundefined

    undefinedundefined

    let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5)];

    undefinedundefined

    undefinedundefined! rabbits.sort(Rabbit.compare); undefinedundefined/!undefinedundefined

    undefinedundefined

    rabbits[0].run(); // Black Rabbit runs with speed 5.

    undefinedundefined

    alert(Rabbit.planet); // Earth

    undefinedundefined

    Now when we call undefinedundefinedRabbit.compare, the inherited undefinedundefinedAnimal.compare will be called.undefinedundefined

    undefinedundefined

    How does it work? Again, using prototypes. As you might have already guessed, undefinedundefinedextends gives undefinedundefinedRabbit the undefinedundefined[[Prototype]] reference to undefinedundefinedAnimal.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    So, undefinedundefinedRabbit extends Animal creates two undefinedundefined[[Prototype]] references:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedRabbit function prototypally inherits from undefinedundefinedAnimal function.undefinedundefined
    2. undefinedundefined
    3. undefinedundefinedRabbit.prototype prototypally inherits from undefinedundefinedAnimal.prototype.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    As a result, inheritance works both for regular and static methods.

    undefinedundefined

    Here, let's check that by code:

    undefinedundefined

    run class Animal {} class Rabbit extends Animal {}

    undefinedundefined

    // for statics alert(Rabbit.__proto__ === Animal); // true

    undefinedundefined

    // for regular methods alert(Rabbit.prototype.__proto__ === Animal.prototype); // true

    undefinedundefined

    Summary

    undefinedundefined

    Static methods are used for the functionality that belongs to the class "as a whole". It doesn't relate to a concrete class instance.

    undefinedundefined

    For example, a method for comparison undefinedundefinedArticle.compare(article1, article2) or a factory method undefinedundefinedArticle.createTodays().undefinedundefined

    undefinedundefined

    They are labeled by the word undefinedundefinedstatic in class declaration.undefinedundefined

    undefinedundefined

    Static properties are used when we'd like to store class-level data, also not bound to an instance.

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    Technically, static declaration is the same as assigning to the class itself:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedMyClass.undefinedundefinedpropertyundefinedundefined= ...undefinedundefinedundefinedundefinedundefinedundefinedMyClass.undefinedundefinedmethodundefinedundefined= ...undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Static properties and methods are inherited.

    undefinedundefined

    For undefinedundefinedclass B extends A the prototype of the class undefinedundefinedB itself points to undefinedundefinedA: undefinedundefinedB.[[Prototype]] = A. So if a field is not found in undefinedundefinedB, the search continues in undefinedundefinedA.undefinedundefined

    undefinedundefined

    Private and protected properties and methods

    undefinedundefined

    One of the most important principles of object oriented programming - delimiting internal interface from the external one.

    undefinedundefined

    That is "a must" practice in developing anything more complex than a "hello world" app.

    undefinedundefined

    To understand this, let's break away from development and turn our eyes into the real world.

    undefinedundefined

    Usually, devices that we're using are quite complex. But delimiting the internal interface from the external one allows to use them without problems.

    undefinedundefined

    A real-life example

    undefinedundefined

    For instance, a coffee machine. Simple from outside: a button, a display, a few holes…And, surely, the result - great coffee! :)

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    But inside… (a picture from the repair manual)

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    A lot of details. But we can use it without knowing anything.

    undefinedundefined

    Coffee machines are quite reliable, aren't they? We can use one for years, and only if something goes wrong - bring it for repairs.

    undefinedundefined

    The secret of reliability and simplicity of a coffee machine - all details are well-tuned and undefinedundefinedhidden inside.undefinedundefined

    undefinedundefined

    If we remove the protective cover from the coffee machine, then using it will be much more complex (where to press?), and dangerous (it can electrocute).

    undefinedundefined

    As we'll see, in programming objects are like coffee machines.

    undefinedundefined

    But in order to hide inner details, we'll use not a protective cover, but rather special syntax of the language and conventions.

    undefinedundefined

    Internal and external interface

    undefinedundefined

    In object-oriented programming, properties and methods are split into two groups:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedInternal interface - methods and properties, accessible from other methods of the class, but not from the outside.undefinedundefined
    • undefinedundefined
    • undefinedundefinedExternal interface - methods and properties, accessible also from outside the class.undefinedundefined
    • undefinedundefined
    undefinedundefined

    If we continue the analogy with the coffee machine - what's hidden inside: a boiler tube, heating element, and so on - is its internal interface.

    undefinedundefined

    An internal interface is used for the object to work, its details use each other. For instance, a boiler tube is attached to the heating element.

    undefinedundefined

    But from the outside a coffee machine is closed by the protective cover, so that no one can reach those. Details are hidden and inaccessible. We can use its features via the external interface.

    undefinedundefined

    So, all we need to use an object is to know its external interface. We may be completely unaware how it works inside, and that's great.

    undefinedundefined

    That was a general introduction.

    undefinedundefined

    In JavaScript, there are two types of object fields (properties and methods):

    undefinedundefined
      undefinedundefined
    • Public: accessible from anywhere. They comprise the external interface. Until now we were only using public properties and methods.
    • undefinedundefined
    • Private: accessible only from inside the class. These are for the internal interface.
    • undefinedundefined
    undefinedundefined

    In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them.

    undefinedundefined

    Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated.

    undefinedundefined

    Now we'll make a coffee machine in JavaScript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could).

    undefinedundefined

    Protecting "waterAmount"

    undefinedundefined

    Let's make a simple coffee machine class first:

    undefinedundefined

    run class CoffeeMachine { waterAmount = 0; // the amount of water inside

    undefinedundefined

    constructor(power) { this.power = power; alert( undefinedundefinedCreated a coffee-machine, power: ${power} ); }undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    // create the coffee machine let coffeeMachine = new CoffeeMachine(100);

    undefinedundefined

    // add water coffeeMachine.waterAmount = 200;

    undefinedundefined

    Right now the properties undefinedundefinedwaterAmount and undefinedundefinedpower are public. We can easily get/set them from the outside to any value.undefinedundefined

    undefinedundefined

    Let's change undefinedundefinedwaterAmount property to protected to have more control over it. For instance, we don't want anyone to set it below zero.undefinedundefined

    undefinedundefined

    undefinedundefinedProtected properties are usually prefixed with an underscore undefinedundefined_.undefinedundefinedundefinedundefined

    undefinedundefined

    That is not enforced on the language level, but there's a well-known convention between programmers that such properties and methods should not be accessed from the outside.

    undefinedundefined

    So our property will be called undefinedundefined_waterAmount:undefinedundefined

    undefinedundefined

    run class CoffeeMachine { _waterAmount = 0;

    undefinedundefined

    set waterAmount(value) { if (value < 0) { value = 0; } this._waterAmount = value; }

    undefinedundefined

    get waterAmount() { return this._waterAmount; }

    undefinedundefined

    constructor(power) { this._power = power; }

    undefinedundefined

    }

    undefinedundefined

    // create the coffee machine let coffeeMachine = new CoffeeMachine(100);

    undefinedundefined

    // add water coffeeMachine.waterAmount = -10; // Error: Negative water

    undefinedundefined

    Now the access is under control, so setting the water amount below zero becomes impossible.

    undefinedundefined

    Read-only "power"

    undefinedundefined

    For undefinedundefinedpower property, let's make it read-only. It sometimes happens that a property must be set at creation time only, and then never modified.undefinedundefined

    undefinedundefined

    That's exactly the case for a coffee machine: power never changes.

    undefinedundefined

    To do so, we only need to make getter, but not the setter:

    undefinedundefined

    run class CoffeeMachine { // …

    undefinedundefined

    constructor(power) { this._power = power; }

    undefinedundefined

    get power() { return this._power; }

    undefinedundefined

    }

    undefinedundefined

    // create the coffee machine let coffeeMachine = new CoffeeMachine(100);

    undefinedundefined

    alert(undefinedundefinedPower is: ${coffeeMachine.power}W); // Power is: 100Wundefinedundefined

    undefinedundefined

    coffeeMachine.power = 25; // Error (no setter)

    undefinedundefined

    smart header="Getter/setter functions" Here we used getter/setter syntax.

    undefinedundefined

    But most of the time undefinedundefinedget.../set... functions are preferred, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now).

    undefinedundefined

    On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide.

    undefinedundefined

    ``undefinedundefinedsmart header="Protected fields are inherited" If we inheritclass MegaMachine extends CoffeeMachineundefinedundefined, then nothing prevents us from accessingthis._waterAmountundefinedundefinedorthis._power` from the methods of the new class.undefinedundefined

    undefinedundefined

    So protected fields are naturally inheritable. Unlike private ones that we'll see below.

    undefinedundefined

    Private "#waterLimit"

    undefinedundefined

    [recent browser=none]

    undefinedundefined

    There's a finished JavaScript proposal, almost in the standard, that provides language-level support for private properties and methods.

    undefinedundefined

    Privates should start with undefinedundefined#. They are only accessible from inside the class.undefinedundefined

    undefinedundefined

    For instance, here's a private undefinedundefined#waterLimit property and the water-checking private method undefinedundefined#checkWater:undefinedundefined

    undefinedundefined

    run class CoffeeMachine { undefinedundefined! #waterLimit = 200; undefinedundefined/!undefinedundefined

    undefinedundefined

    undefinedundefined! #fixWaterAmount(value) { if (value < 0) return 0; if (value > this.#waterLimit) return this.#waterLimit; } undefinedundefined/!undefinedundefined

    undefinedundefined

    setWaterAmount(value) { this.#waterLimit = this.#fixWaterAmount(value); }

    undefinedundefined

    }

    undefinedundefined

    let coffeeMachine = new CoffeeMachine();

    undefinedundefined

    undefinedundefined! // can't access privates from outside of the class coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error undefinedundefined/! undefinedundefined

    undefinedundefined

    On the language level, undefinedundefined# is a special sign that the field is private. We can't access it from outside or from inheriting classes.undefinedundefined

    undefinedundefined

    Private fields do not conflict with public ones. We can have both private undefinedundefined#waterAmount and public undefinedundefinedwaterAmount fields at the same time.undefinedundefined

    undefinedundefined

    For instance, let's make undefinedundefinedwaterAmount an accessor for undefinedundefined#waterAmount:undefinedundefined

    undefinedundefined

    run class CoffeeMachine {

    undefinedundefined

    #waterAmount = 0;

    undefinedundefined

    get waterAmount() { return this.#waterAmount; }

    undefinedundefined

    set waterAmount(value) { if (value < 0) value = 0; this.#waterAmount = value; } }

    undefinedundefined

    let machine = new CoffeeMachine();

    undefinedundefined

    machine.waterAmount = 100; alert(machine.#waterAmount); // Error

    undefinedundefined

    Unlike protected ones, private fields are enforced by the language itself. That's a good thing.

    undefinedundefined

    But if we inherit from undefinedundefinedCoffeeMachine, then we'll have no direct access to undefinedundefined#waterAmount. We'll need to rely on undefinedundefinedwaterAmount getter/setter:undefinedundefined

    undefinedundefinedundefinedundefined

    In many scenarios such limitation is too severe. If we extend a undefinedundefinedCoffeeMachine, we may have legitimate reasons to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax.undefinedundefined

    undefinedundefined

    warn header="Private fields are not available as this[name]" Private fields are special.

    undefinedundefined

    As we know, usually we can access fields using undefinedundefinedthis[name]:undefinedundefined

    undefinedundefinedundefinedundefined

    With private fields that's impossible: undefinedundefinedthis['#name'] doesn't work. That's a syntax limitation to ensure privacy. undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    In terms of OOP, delimiting of the internal interface from the external one is called undefinedundefinedencapsulation.undefinedundefined

    undefinedundefined

    It gives the following benefits:

    undefinedundefined
    undefinedundefined
    Protection for users, so that they don't shoot themselves in the foot
    undefinedundefined
    undefinedundefined

    Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed.

    undefinedundefined

    All developers are civilized - they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later.

    undefinedundefined

    That's surely not John's fault, but rather the person who removed the protective cover and let John do his manipulations.

    undefinedundefined

    The same in programming. If a user of a class will change things not intended to be changed from the outside - the consequences are unpredictable.

    undefinedundefined
    undefinedundefined
    Supportable
    undefinedundefined
    undefinedundefined

    The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement.

    undefinedundefined

    undefinedundefinedIf we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users.undefinedundefined

    undefinedundefined

    If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them.

    undefinedundefined

    For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same.

    undefinedundefined
    undefinedundefined
    Hiding complexity
    undefinedundefined
    undefinedundefined

    People adore using things that are simple. At least from outside. What's inside is a different thing.

    undefinedundefined

    Programmers are not an exception.

    undefinedundefined

    undefinedundefinedIt's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined

    To hide an internal interface we use either protected or private properties:

    undefinedundefined
      undefinedundefined
    • Protected fields start with undefinedundefined_. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with undefinedundefined_ from its class and classes inheriting from it.undefinedundefined
    • undefinedundefined
    • Private fields start with undefinedundefined#. JavaScript makes sure we can only access those from inside the class.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Right now, private fields are not well-supported among browsers, but can be polyfilled.

    undefinedundefined

    The modern mode, "use strict"

    undefinedundefined

    For a long time, JavaScript evolved without compatibility issues. New features were added to the language while old functionality didn't change.

    undefinedundefined

    That had the benefit of never breaking existing code. But the downside was that any mistake or an imperfect decision made by JavaScript's creators got stuck in the language forever.

    undefinedundefined

    This was the case until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most such modifications are off by default. You need to explicitly enable them with a special directive: undefinedundefined"use strict".undefinedundefined

    undefinedundefined

    "use strict"

    undefinedundefined

    The directive looks like a string: undefinedundefined"use strict" or undefinedundefined'use strict'. When it is located at the top of a script, the whole script works the "modern" way.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined"use strict"undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// this code works the modern wayundefinedundefinedundefinedundefined...undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Quite soon we're going to learn functions (a way to group commands), so let's note in advance that undefinedundefined"use strict" can be put at the beginning of a function. Doing that enables strict mode in that function only. But usually people use it for the whole script.undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Ensure that \"use strict\" is at the top" Please make sure that"use strict"` is at the top of your scripts, otherwise strict mode may not be enabled.undefinedundefined

    undefinedundefined

    Strict mode isn't enabled here:

    undefinedundefined

    no-strict alert("some code"); // "use strict" below is ignored-it must be at the top

    undefinedundefined

    "use strict";

    undefinedundefined

    // strict mode is not activated

    undefinedundefined
    undefinedundefined
    Only comments may appear above `"use strict"`.undefinedundefined
    undefinedundefined

    ``undefinedundefinedwarn header="There's no way to canceluse strictundefinedundefined" There is no directive like"no use strict"` that reverts the engine to old behavior.undefinedundefined

    undefinedundefined

    Once we enter strict mode, there's no going back.

    undefinedundefined

    Browser console

    undefinedundefined

    When you use a undefinedundefineddeveloper console to run code, please note that it doesn't undefinedundefineduse strict by default.undefinedundefined

    undefinedundefined

    Sometimes, when undefinedundefineduse strict makes a difference, you'll get incorrect results.undefinedundefined

    undefinedundefined

    So, how to actually undefinedundefineduse strict in the console?undefinedundefined

    undefinedundefined

    First, you can try to press undefinedundefinedkey:Shift+Enter to input multiple lines, and put undefinedundefineduse strict on top, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    It works in most browsers, namely Firefox and Chrome.

    undefinedundefined

    If it doesn't, e.g. in an old browser, there's an ugly, but reliable way to ensure undefinedundefineduse strict. Put it inside this kind of wrapper:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefined(undefinedundefinedfunction() undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined'use strict'undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// ...your code here...undefinedundefinedundefinedundefinedundefinedundefined})()undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Should we "use strict"?

    undefinedundefined

    The question may sound obvious, but it's not so.

    undefinedundefined

    One could recommend to start scripts with undefinedundefined"use strict"… But you know what's cool?undefinedundefined

    undefinedundefined

    Modern JavaScript supports "classes" and "modules" - advanced language structures (we'll surely get to them), that enable undefinedundefineduse strict automatically. So we don't need to add the undefinedundefined"use strict" directive, if we use them.undefinedundefined

    undefinedundefined

    undefinedundefinedSo, for now undefinedundefined"use strict"; is a welcome guest at the top of your scripts. Later, when your code is all in classes and modules, you may omit it.undefinedundefinedundefinedundefined

    undefinedundefined

    As of now, we've got to know about undefinedundefineduse strict in general.undefinedundefined

    undefinedundefined

    In the next chapters, as we learn language features, we'll see the differences between the strict and old modes. Luckily, there aren't many and they actually make our lives better.

    undefinedundefined

    All examples in this tutorial assume strict mode unless (very rarely) specified otherwise.

    undefinedundefined

    Extending built-in classes

    undefinedundefined

    Built-in classes like Array, Map and others are extendable also.

    undefinedundefined

    For instance, here undefinedundefinedPowerArray inherits from the native undefinedundefinedArray:undefinedundefined

    undefinedundefined

    run // add one more method to it (can do more) class PowerArray extends Array { isEmpty() { return this.length === 0; } }

    undefinedundefined

    let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false

    undefinedundefined

    let filteredArr = arr.filter(item => item >= 10); alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false

    undefinedundefined

    Please note a very interesting thing. Built-in methods like undefinedundefinedfilter, undefinedundefinedmap and others - return new objects of exactly the inherited type undefinedundefinedPowerArray. Their internal implementation uses the object's undefinedundefinedconstructor property for that.undefinedundefined

    undefinedundefined

    In the example above,

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedarr.undefinedundefinedconstructorundefinedundefined=== PowerArrayundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    When undefinedundefinedarr.filter() is called, it internally creates the new array of results using exactly undefinedundefinedarr.constructor, not basic undefinedundefinedArray. That's actually very cool, because we can keep using undefinedundefinedPowerArray methods further on the result.undefinedundefined

    undefinedundefined

    Even more, we can customize that behavior.

    undefinedundefined

    We can add a special static getter undefinedundefinedSymbol.species to the class. If it exists, it should return the constructor that JavaScript will use internally to create new entities in undefinedundefinedmap, undefinedundefinedfilter and so on.undefinedundefined

    undefinedundefined

    If we'd like built-in methods like undefinedundefinedmap or undefinedundefinedfilter to return regular arrays, we can return undefinedundefinedArray in undefinedundefinedSymbol.species, like here:undefinedundefined

    undefinedundefined

    run class PowerArray extends Array { isEmpty() { return this.length === 0; }

    undefinedundefined

    undefinedundefined! // built-in methods will use this as the constructor static get undefinedundefinedSymbol.species { return Array; } undefinedundefined/! }undefinedundefined

    undefinedundefined

    let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false

    undefinedundefined

    // filter creates new array using arr.constructor[Symbol.species] as constructor let filteredArr = arr.filter(item => item >= 10);

    undefinedundefined

    undefinedundefined! // filteredArr is not PowerArray, but Array undefinedundefined/! alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function undefinedundefined

    undefinedundefined

    As you can see, now undefinedundefined.filter returns undefinedundefinedArray. So the extended functionality is not passed any further.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Other collections work similarly" Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`.undefinedundefined

    undefinedundefined

    No static inheritance in built-ins

    undefinedundefined

    Built-in objects have their own static methods, for instance undefinedundefinedObject.keys, undefinedundefinedArray.isArray etc.undefinedundefined

    undefinedundefined

    As we already know, native classes extend each other. For instance, undefinedundefinedArray extends undefinedundefinedObject.undefinedundefined

    undefinedundefined

    Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the article undefinedundefined.undefinedundefined

    undefinedundefined

    But built-in classes are an exception. They don't inherit statics from each other.

    undefinedundefined

    For example, both undefinedundefinedArray and undefinedundefinedDate inherit from undefinedundefinedObject, so their instances have methods from undefinedundefinedObject.prototype. But undefinedundefinedArray.[[Prototype]] does not reference undefinedundefinedObject, so there's no, for instance, undefinedundefinedArray.keys() (or undefinedundefinedDate.keys()) static method.undefinedundefined

    undefinedundefined

    Here's the picture structure for undefinedundefinedDate and undefinedundefinedObject:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    As you can see, there's no link between undefinedundefinedDate and undefinedundefinedObject. They are independent, only undefinedundefinedDate.prototype inherits from undefinedundefinedObject.prototype.undefinedundefined

    undefinedundefined

    That's an important difference of inheritance between built-in objects compared to what we get with undefinedundefinedextends.undefinedundefined

    undefinedundefined

    Class checking: "instanceof"

    undefinedundefined

    The undefinedundefinedinstanceof operator allows to check whether an object belongs to a certain class. It also takes inheritance into account.undefinedundefined

    undefinedundefined

    Such a check may be necessary in many cases. For example, it can be used for building a undefinedundefinedpolymorphic function, the one that treats arguments differently depending on their type.undefinedundefined

    undefinedundefined

    The instanceof operator [#ref-instanceof]

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedobj undefinedundefinedinstanceof Classundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It returns undefinedundefinedtrue if undefinedundefinedobj belongs to the undefinedundefinedClass or a class inheriting from it.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run class Rabbit {} let rabbit = new Rabbit();

    undefinedundefined

    // is it an object of Rabbit class? undefinedundefined! alert( rabbit instanceof Rabbit ); // true undefinedundefined/! undefinedundefined

    undefinedundefined

    It also works with constructor functions:

    undefinedundefined

    run undefinedundefined! // instead of class function Rabbit() {} undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( new Rabbit() instanceof Rabbit ); // true

    undefinedundefined

    …And with built-in classes like undefinedundefinedArray:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // trueundefinedundefined

    undefinedundefined

    Please note that undefinedundefinedarr also belongs to the undefinedundefinedObject class. That's because undefinedundefinedArray prototypically inherits from undefinedundefinedObject.undefinedundefined

    undefinedundefined

    Normally, undefinedundefinedinstanceof examines the prototype chain for the check. We can also set a custom logic in the static method undefinedundefinedSymbol.hasInstance.undefinedundefined

    undefinedundefined

    The algorithm of undefinedundefinedobj instanceof Class works roughly as follows:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      If there's a static method undefinedundefinedSymbol.hasInstance, then just call it: undefinedundefinedClass[Symbol.hasInstance](obj). It should return either undefinedundefinedtrue or undefinedundefinedfalse, and we're done. That's how we can customize the behavior of undefinedundefinedinstanceof.undefinedundefined

      undefinedundefined

      For example:

      undefinedundefined

      run // setup instanceOf check that assumes that // anything with canEat property is an animal class Animal { static undefinedundefinedSymbol.hasInstance { if (obj.canEat) return true; } }undefinedundefined

      undefinedundefined

      let obj = { canEat: true };

      undefinedundefined

      alert(obj instanceof Animal); // true: AnimalundefinedundefinedSymbol.hasInstance is called undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      Most classes do not have undefinedundefinedSymbol.hasInstance. In that case, the standard logic is used: undefinedundefinedobj instanceOf Class checks whether undefinedundefinedClass.prototype is equal to one of the prototypes in the undefinedundefinedobj prototype chain.undefinedundefined

      undefinedundefined

      In other words, compare one after another: undefinedundefinedjs obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // if any answer is true, return true // otherwise, if we reached the end of the chain, return falseundefinedundefined

      undefinedundefined

      In the example above undefinedundefinedrabbit.__proto__ === Rabbit.prototype, so that gives the answer immediately.undefinedundefined

      undefinedundefined

      In the case of an inheritance, the match will be at the second step:

      undefinedundefined

      run class Animal {} class Rabbit extends Animal {}

      undefinedundefined

      let rabbit = new Rabbit(); undefinedundefined! alert(rabbit instanceof Animal); // true undefinedundefined/!undefinedundefined

      undefinedundefined

      // rabbit.__proto__ === Rabbit.prototype undefinedundefined! // rabbit.__proto__.__proto__ === Animal.prototype (match!) undefinedundefined/! undefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Here's the illustration of what undefinedundefinedrabbit instanceof Animal compares with undefinedundefinedAnimal.prototype:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    By the way, there's also a method undefinedundefinedobjA.isPrototypeOf(objB), that returns undefinedundefinedtrue if undefinedundefinedobjA is somewhere in the chain of prototypes for undefinedundefinedobjB. So the test of undefinedundefinedobj instanceof Class can be rephrased as undefinedundefinedClass.prototype.isPrototypeOf(obj).undefinedundefined

    undefinedundefined

    It's funny, but the undefinedundefinedClass constructor itself does not participate in the check! Only the chain of prototypes and undefinedundefinedClass.prototype matters.undefinedundefined

    undefinedundefined

    That can lead to interesting consequences when a undefinedundefinedprototype property is changed after the object is created.undefinedundefined

    undefinedundefined

    Like here:

    undefinedundefined

    run function Rabbit() {} let rabbit = new Rabbit();

    undefinedundefined

    // changed the prototype Rabbit.prototype = {};

    undefinedundefined

    // …not a rabbit any more! undefinedundefined! alert( rabbit instanceof Rabbit ); // false undefinedundefined/! undefinedundefined

    undefinedundefined

    Bonus: Object.prototype.toString for the type

    undefinedundefined

    We already know that plain objects are converted to string as undefinedundefined[object Object]:undefinedundefined

    undefinedundefined

    run let obj = {};

    undefinedundefined

    alert(obj); // [object Object] alert(obj.toString()); // the same

    undefinedundefined

    That's their implementation of undefinedundefinedtoString. But there's a hidden feature that makes undefinedundefinedtoString actually much more powerful than that. We can use it as an extended undefinedundefinedtypeof and an alternative for undefinedundefinedinstanceof.undefinedundefined

    undefinedundefined

    Sounds strange? Indeed. Let's demystify.

    undefinedundefined

    By undefinedundefinedspecification, the built-in undefinedundefinedtoString can be extracted from the object and executed in the context of any other value. And its result depends on that value.undefinedundefined

    undefinedundefined
      undefinedundefined
    • For a number, it will be undefinedundefined[object Number]undefinedundefined
    • undefinedundefined
    • For a boolean, it will be undefinedundefined[object Boolean]undefinedundefined
    • undefinedundefined
    • For undefinedundefinednull: undefinedundefined[object Null]undefinedundefined
    • undefinedundefined
    • For undefinedundefinedundefined: undefinedundefined[object Undefined]undefinedundefined
    • undefinedundefined
    • For arrays: undefinedundefined[object Array]undefinedundefined
    • undefinedundefined
    • …etc (customizable).
    • undefinedundefined
    undefinedundefined

    Let's demonstrate:

    undefinedundefined

    run // copy toString method into a variable for convenience let objectToString = Object.prototype.toString;

    undefinedundefined

    // what type is this? let arr = [];

    undefinedundefined

    alert( objectToString.call(arr) ); // [object undefinedundefined!Arrayundefinedundefined/!] undefinedundefined

    undefinedundefined

    Here we used undefinedundefinedcall as described in the chapter undefinedundefined to execute the function undefinedundefinedobjectToString in the context undefinedundefinedthis=arr.undefinedundefined

    undefinedundefined

    Internally, the undefinedundefinedtoString algorithm examines undefinedundefinedthis and returns the corresponding result. More examples:undefinedundefined

    undefinedundefined

    run let s = Object.prototype.toString;

    undefinedundefined

    alert( s.call(123) ); // [object Number] alert( s.call(null) ); // [object Null] alert( s.call(alert) ); // [object Function]

    undefinedundefined

    Symbol.toStringTag

    undefinedundefined

    The behavior of Object undefinedundefinedtoString can be customized using a special object property undefinedundefinedSymbol.toStringTag.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let user = { undefinedundefinedSymbol.toStringTag: "User" };undefinedundefined

    undefinedundefined

    alert( {}.toString.call(user) ); // [object User]

    undefinedundefined

    For most environment-specific objects, there is such a property. Here are some browser specific examples:

    undefinedundefined

    run // toStringTag for the environment-specific object and class: alert( windowundefinedundefinedSymbol.toStringTag); // Window alert( XMLHttpRequest.prototypeundefinedundefinedSymbol.toStringTag ); // XMLHttpRequestundefinedundefined

    undefinedundefined

    alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]

    undefinedundefined

    As you can see, the result is exactly undefinedundefinedSymbol.toStringTag (if exists), wrapped into undefinedundefined[object ...].undefinedundefined

    undefinedundefined

    At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized.

    undefinedundefined

    We can use undefinedundefined{}.toString.call instead of undefinedundefinedinstanceof for built-in objects when we want to get the type as a string rather than just to check.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Let's summarize the type-checking methods that we know:

    undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    works forreturns
    undefinedundefinedtypeofundefinedundefinedprimitivesstring
    undefinedundefined{}.toStringundefinedundefinedprimitives, built-in objects, objects with undefinedundefinedSymbol.toStringTagundefinedundefinedstring
    undefinedundefinedinstanceofundefinedundefinedobjectstrue/false
    undefinedundefined

    As we can see, undefinedundefined{}.toString is technically a "more advanced" undefinedundefinedtypeof.undefinedundefined

    undefinedundefined

    And undefinedundefinedinstanceof operator really shines when we are working with a class hierarchy and want to check for the class taking into account inheritance.undefinedundefined

    undefinedundefined

    Mixins

    undefinedundefined

    In JavaScript we can only inherit from a single object. There can be only one undefinedundefined[[Prototype]] for an object. And a class may extend only one other class.undefinedundefined

    undefinedundefined

    But sometimes that feels limiting. For instance, we have a class undefinedundefinedStreetSweeper and a class undefinedundefinedBicycle, and want to make their mix: a undefinedundefinedStreetSweepingBicycle.undefinedundefined

    undefinedundefined

    Or we have a class undefinedundefinedUser and a class undefinedundefinedEventEmitter that implements event generation, and we'd like to add the functionality of undefinedundefinedEventEmitter to undefinedundefinedUser, so that our users can emit events.undefinedundefined

    undefinedundefined

    There's a concept that can help here, called "mixins".

    undefinedundefined

    As defined in Wikipedia, a undefinedundefinedmixin is a class containing methods that can be used by other classes without a need to inherit from it.undefinedundefined

    undefinedundefined

    In other words, a undefinedundefinedmixin provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.undefinedundefined

    undefinedundefined

    A mixin example

    undefinedundefined

    The simplest way to implement a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.

    undefinedundefined

    For instance here the mixin undefinedundefinedsayHiMixin is used to add some "speech" for undefinedundefinedUser:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run *!* // mixin */!* let sayHiMixin = { sayHi() { alert(Hello ${this.name}undefinedundefined); }, sayBye() { alert(Bye ${this.name}`); } };undefinedundefined

    undefinedundefined

    undefinedundefined! // usage: undefinedundefined/! class User { constructor(name) { this.name = name; } }undefinedundefined

    undefinedundefined

    // copy the methods Object.assign(User.prototype, sayHiMixin);

    undefinedundefined

    // now User can say hi new User("Dude").sayHi(); // Hello Dude!

    undefinedundefined

    There's no inheritance, but a simple method copying. So undefinedundefinedUser may inherit from another class and also include the mixin to "mix-in" the additional methods, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    Mixins can make use of inheritance inside themselves.

    undefinedundefined

    For instance, here undefinedundefinedsayHiMixin inherits from undefinedundefinedsayMixin:undefinedundefined

    undefinedundefined

    run let sayMixin = { say(phrase) { alert(phrase); } };

    undefinedundefined

    let sayHiMixin = { undefinedundefinedproto: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here)undefinedundefined

    undefinedundefined

    sayHi() { undefinedundefined! // call parent method undefinedundefined/! super.say(undefinedundefinedHello ${this.name}); // (undefinedundefined) }, sayBye() { super.say(undefinedundefinedBye ${this.name}); // (undefinedundefined) } };undefinedundefined

    undefinedundefined

    class User { constructor(name) { this.name = name; } }

    undefinedundefined

    // copy the methods Object.assign(User.prototype, sayHiMixin);

    undefinedundefined

    // now User can say hi new User("Dude").sayHi(); // Hello Dude!

    undefinedundefined

    Please note that the call to the parent method undefinedundefinedsuper.say() from undefinedundefinedsayHiMixin (at lines labelled with undefinedundefined(*)) looks for the method in the prototype of that mixin, not the class.undefinedundefined

    undefinedundefined

    Here's the diagram (see the right part):

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    That's because methods undefinedundefinedsayHi and undefinedundefinedsayBye were initially created in undefinedundefinedsayHiMixin. So even though they got copied, their undefinedundefined[[HomeObject]] internal property references undefinedundefinedsayHiMixin, as shown in the picture above.undefinedundefined

    undefinedundefined

    As undefinedundefinedsuper looks for parent methods in undefinedundefined[[HomeObject]].[[Prototype]], that means it searches undefinedundefinedsayHiMixin.[[Prototype]], not undefinedundefinedUser.[[Prototype]].undefinedundefined

    undefinedundefined

    EventMixin

    undefinedundefined

    Now let's make a mixin for real life.

    undefinedundefined

    An important feature of many browser objects (for instance) is that they can generate events. Events are a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows us to easily add event-related functions to any class/object.

    undefinedundefined
      undefinedundefined
    • The mixin will provide a method undefinedundefined.trigger(name, [...data]) to "generate an event" when something important happens to it. The undefinedundefinedname argument is a name of the event, optionally followed by additional arguments with event data.undefinedundefined
    • undefinedundefined
    • Also the method undefinedundefined.on(name, handler) that adds undefinedundefinedhandler function as the listener to events with the given name. It will be called when an event with the given undefinedundefinedname triggers, and get the arguments from the undefinedundefined.trigger call.undefinedundefined
    • undefinedundefined
    • …And the method undefinedundefined.off(name, handler) that removes the undefinedundefinedhandler listener.undefinedundefined
    • undefinedundefined
    undefinedundefined

    After adding the mixin, an object undefinedundefineduser will be able to generate an event undefinedundefined"login" when the visitor logs in. And another object, say, undefinedundefinedcalendar may want to listen for such events to load the calendar for the logged-in person.undefinedundefined

    undefinedundefined

    Or, a undefinedundefinedmenu can generate the event undefinedundefined"select" when a menu item is selected, and other objects may assign handlers to react on that event. And so on.undefinedundefined

    undefinedundefined

    Here's the code:

    undefinedundefined

    run let eventMixin = { /** * Subscribe to event, usage: * menu.on(‘select', function(item) { … } */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); },

    undefinedundefined

    /** * Cancel the subscription, usage: * menu.off(‘select', handler) */ off(eventName, handler) { let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { handlers.splice(i-, 1); } } },

    undefinedundefined

    /** * Generate an event with the given name and data * this.trigger(‘select', data1, data2); */ trigger(eventName, …args) { if (!this._eventHandlers?.[eventName]) { return; // no handlers for that event name }

    undefinedundefined
    undefinedundefined// call the handlers
    this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));undefinedundefined
    undefinedundefined

    } };

    undefinedundefined
      undefinedundefined
    • undefinedundefined.on(eventName, handler) - assigns function undefinedundefinedhandler to run when the event with that name occurs. Technically, there's an undefinedundefined_eventHandlers property that stores an array of handlers for each event name, and it just adds it to the list.undefinedundefined
    • undefinedundefined
    • undefinedundefined.off(eventName, handler) - removes the function from the handlers list.undefinedundefined
    • undefinedundefined
    • undefinedundefined.trigger(eventName, ...args) - generates the event: all handlers from undefinedundefined_eventHandlers[eventName] are called, with a list of arguments undefinedundefined...args.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Usage:

    undefinedundefined

    run // Make a class class Menu { choose(value) { this.trigger("select", value); } } // Add the mixin with event-related methods Object.assign(Menu.prototype, eventMixin);

    undefinedundefined

    let menu = new Menu();

    undefinedundefined

    // add a handler, to be called on selection: undefinedundefined! menu.on("select", value => alert(undefinedundefinedValue selected: ${value})); undefinedundefined/!undefinedundefined

    undefinedundefined

    // triggers the event => the handler above runs and shows: // Value selected: 123 menu.choose("123");

    undefinedundefined

    Now, if we'd like any code to react to a menu selection, we can listen for it with undefinedundefinedmenu.on(...).undefinedundefined

    undefinedundefined

    And undefinedundefinedeventMixin mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedMixin - is a generic object-oriented programming term: a class that contains methods for other classes.undefinedundefined

    undefinedundefined

    Some other languages allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.

    undefinedundefined

    We can use mixins as a way to augment a class by adding multiple behaviors, like event-handling as we have seen above.

    undefinedundefined

    Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that happening.

    undefinedundefined

    Error handling, "try…catch"

    undefinedundefined

    No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons.

    undefinedundefined

    Usually, a script "dies" (immediately stops) in case of an error, printing it to console.

    undefinedundefined

    But there's a syntax construct undefinedundefinedtry...catch that allows us to "catch" errors so the script can, instead of dying, do something more reasonable.undefinedundefined

    undefinedundefined

    The "try…catch" syntax

    undefinedundefined

    The undefinedundefinedtry...catch construct has two main blocks: undefinedundefinedtry, and then undefinedundefinedcatch:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedtryundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// code...undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedcatch (err) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// error handlingundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    It works like this:

    undefinedundefined
      undefinedundefined
    1. First, the code in undefinedundefinedtry {...} is executed.undefinedundefined
    2. undefinedundefined
    3. If there were no errors, then undefinedundefinedcatch (err) is ignored: the execution reaches the end of undefinedundefinedtry and goes on, skipping undefinedundefinedcatch.undefinedundefined
    4. undefinedundefined
    5. If an error occurs, then the undefinedundefinedtry execution is stopped, and control flows to the beginning of undefinedundefinedcatch (err). The undefinedundefinederr variable (we can use any name for it) will contain an error object with details about what happened.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    So, an error inside the undefinedundefinedtry {...} block does not kill the script - we have a chance to handle it in undefinedundefinedcatch.undefinedundefined

    undefinedundefined

    Let's look at some examples.

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      An errorless example: shows undefinedundefinedalertundefinedundefined(1) and undefinedundefined(2):undefinedundefined

      undefinedundefined

      run try {

      undefinedundefined
      undefinedundefinedalert('Start of try runs');  // *!*(1) <--*/!*
      // ...no errors here
      alert('End of try runs');   // *!*(2) <--*/!*undefinedundefined
      undefinedundefined

      } catch (err) {

      undefinedundefined
      undefinedundefinedalert('Catch is ignored, because there are no errors'); // (3)undefinedundefined
      } undefinedundefined
    • undefinedundefined
    • undefinedundefined

      An example with an error: shows undefinedundefined(1) and undefinedundefined(3):undefinedundefined

      undefinedundefined

      run try {

      undefinedundefined
      undefinedundefinedalert('Start of try runs');  // *!*(1) <--*/!*undefinedundefined
      undefinedundefined

      undefinedundefined! lalala; // error, variable is not defined! undefinedundefined/!undefinedundefined

      undefinedundefined
      undefinedundefinedalert('End of try (never reached)');  // (2)undefinedundefined
      undefinedundefined

      } catch (err) {

      undefinedundefined
      undefinedundefinedalert(`Error has occurred!`); // *!*(3) <--*/!*undefinedundefined
      undefinedundefined

      }

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="try…catchundefinedundefinedonly works for runtime errors" Fortry…catch` to work, the code must be runnable. In other words, it should be valid JavaScript.undefinedundefined

    undefinedundefined

    It won't work if the code is syntactically wrong, for instance it has unmatched curly braces:

    undefinedundefined

    undefinedundefinedjs run try { {{{{{{{{{{{{ } catch (err) { alert("The engine can't understand this code, it's invalid"); }undefinedundefined

    undefinedundefined

    The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code.

    undefinedundefined

    So, undefinedundefinedtry...catch can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="try…catchundefinedundefinedworks synchronously" If an exception happens in "scheduled" code, like insetTimeoutundefinedundefined, thentry…catch` won't catch it:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run try { setTimeout(function() { noSuchVariable; // script will die here }, 1000); } catch (err) { alert( "won't work" ); }undefinedundefined

    undefinedundefined

    That's because the function itself is executed later, when the engine has already left the undefinedundefinedtry...catch construct.undefinedundefined

    undefinedundefined

    To catch an exception inside a scheduled function, undefinedundefinedtry...catch must be inside that function: undefinedundefinedjs run setTimeout(function() { try { noSuchVariable; // try...catch handles the error! } catch { alert( "error is caught here!" ); } }, 1000); undefinedundefined

    undefinedundefined

    Error object

    undefinedundefined

    When an error occurs, JavaScript generates an object containing the details about it. The object is then passed as an argument to undefinedundefinedcatch:undefinedundefined

    undefinedundefinedundefinedundefined

    For all built-in errors, the error object has two main properties:

    undefinedundefined
    undefinedundefined
    undefinedundefinednameundefinedundefined
    undefinedundefined
    Error name. For instance, for an undefined variable that's undefinedundefined"ReferenceError". undefinedundefined
    undefinedundefined
    undefinedundefinedmessageundefinedundefined
    undefinedundefined
    Textual message about error details.
    undefinedundefined
    undefinedundefined

    There are other non-standard properties available in most environments. One of most widely used and supported is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedstackundefinedundefined
    undefinedundefined
    Current call stack: a string with information about the sequence of nested calls that led to the error. Used for debugging purposes.
    undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    run untrusted try { undefinedundefined! lalala; // error, variable is not defined! undefinedundefined/! } catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (…call stack)undefinedundefined

    undefinedundefined

    // Can also show an error as a whole // The error is converted to string as "name: message" alert(err); // ReferenceError: lalala is not defined }

    undefinedundefined

    Optional "catch" binding

    undefinedundefined

    [recent browser=new]

    undefinedundefined

    If we don't need error details, undefinedundefinedcatch may omit it:undefinedundefined

    undefinedundefinedundefinedundefined

    Using "try…catch"

    undefinedundefined

    Let's explore a real-life use case of undefinedundefinedtry...catch.undefinedundefined

    undefinedundefined

    As we already know, JavaScript supports the undefinedundefinedJSON.parse(str) method to read JSON-encoded values.undefinedundefined

    undefinedundefined

    Usually it's used to decode data received over the network, from the server or another source.

    undefinedundefined

    We receive it and call undefinedundefinedJSON.parse like this:undefinedundefined

    undefinedundefined

    run let json = ‘{"name":"John", "age": 30}'''; // data from the server

    undefinedundefined

    undefinedundefined! let user = JSON.parse(json); // convert the text representation to JS object undefinedundefined/!undefinedundefined

    undefinedundefined

    // now user is an object with properties from the string alert( user.name ); // John alert( user.age ); // 30

    undefinedundefined

    You can find more detailed information about JSON in the undefinedundefinedinfo:json chapter.undefinedundefined

    undefinedundefined

    undefinedundefinedIf undefinedundefinedjson is malformed, undefinedundefinedJSON.parse generates an error, so the script "dies".undefinedundefinedundefinedundefined

    undefinedundefined

    Should we be satisfied with that? Of course not!

    undefinedundefined

    This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message.

    undefinedundefined

    Let's use undefinedundefinedtry...catch to handle the error:undefinedundefined

    undefinedundefined

    run let json = "{ bad json }";

    undefinedundefined

    try {

    undefinedundefined

    undefinedundefined! let user = JSON.parse(json); // <- when an error occurs… undefinedundefined/! alert( user.name ); // doesn't workundefinedundefined

    undefinedundefined

    } catch (err) { undefinedundefined! // …the execution jumps here alert( "Our apologies, the data has errors, we'll try to request it one more time." ); alert( err.name ); alert( err.message ); undefinedundefined/! } undefinedundefined

    undefinedundefined

    Here we use the undefinedundefinedcatch block only to show the message, but we can do much more: send a new network request, suggest an alternative to the visitor, send information about the error to a logging facility, … . All much better than just dying.undefinedundefined

    undefinedundefined

    Throwing our own errors

    undefinedundefined

    What if undefinedundefinedjson is syntactically correct, but doesn't have a required undefinedundefinedname property?undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    run let json = ‘{ "age": 30 }'''; // incomplete data

    undefinedundefined

    try {

    undefinedundefined

    let user = JSON.parse(json); // <- no errors undefinedundefined! alert( user.name ); // no name! undefinedundefined/!undefinedundefined

    undefinedundefined

    } catch (err) { alert( "doesn't execute" ); }

    undefinedundefined

    Here undefinedundefinedJSON.parse runs normally, but the absence of undefinedundefinedname is actually an error for us.undefinedundefined

    undefinedundefined

    To unify error handling, we'll use the undefinedundefinedthrow operator.undefinedundefined

    undefinedundefined

    "Throw" operator

    undefinedundefined

    The undefinedundefinedthrow operator generates an error.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedthrowundefinedundefined<error objectundefinedundefined>undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Technically, we can use anything as an error object. That may be even a primitive, like a number or a string, but it's better to use objects, preferably with undefinedundefinedname and undefinedundefinedmessage properties (to stay somewhat compatible with built-in errors).undefinedundefined

    undefinedundefined

    JavaScript has many built-in constructors for standard errors: undefinedundefinedError, undefinedundefinedSyntaxError, undefinedundefinedReferenceError, undefinedundefinedTypeError and others. We can use them to create error objects as well.undefinedundefined

    undefinedundefined

    Their syntax is:

    undefinedundefinedundefinedundefined

    For built-in errors (not for any objects, just for errors), the undefinedundefinedname property is exactly the name of the constructor. And undefinedundefinedmessage is taken from the argument.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let error = new Error("Things happen o_O");

    undefinedundefined

    alert(error.name); // Error alert(error.message); // Things happen o_O

    undefinedundefined

    Let's see what kind of error undefinedundefinedJSON.parse generates:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run try { JSON.parse("{ bad json o_O }"); } catch (err) { *!* alert(err.name); // SyntaxError */!* alert(err.message); // Unexpected token b in JSON at position 2 }undefinedundefined

    undefinedundefined

    As we can see, that's a undefinedundefinedSyntaxError.undefinedundefined

    undefinedundefined

    And in our case, the absence of undefinedundefinedname is an error, as users must have a undefinedundefinedname.undefinedundefined

    undefinedundefined

    So let's throw it:

    undefinedundefined

    run let json = ‘{ "age": 30 }'''; // incomplete data

    undefinedundefined

    try {

    undefinedundefined

    let user = JSON.parse(json); // <- no errors

    undefinedundefined

    if (!user.name) { undefinedundefined! throw new SyntaxError("Incomplete data: no name"); // (undefinedundefined) /!* }undefinedundefined

    undefinedundefined

    alert( user.name );

    undefinedundefined

    } catch (err) { alert( "JSON Error:" + err.message ); // JSON Error: Incomplete data: no name }

    undefinedundefined

    In the line undefinedundefined(*), the undefinedundefinedthrow operator generates a undefinedundefinedSyntaxError with the given undefinedundefinedmessage, the same way as JavaScript would generate it itself. The execution of undefinedundefinedtry immediately stops and the control flow jumps into undefinedundefinedcatch.undefinedundefined

    undefinedundefined

    Now undefinedundefinedcatch became a single place for all error handling: both for undefinedundefinedJSON.parse and other cases.undefinedundefined

    undefinedundefined

    Rethrowing

    undefinedundefined

    In the example above we use undefinedundefinedtry...catch to handle incorrect data. But is it possible that undefinedundefinedanother unexpected error occurs within the undefinedundefinedtry {...} block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing.undefinedundefined

    undefinedundefined

    For example:

    undefinedundefined

    run let json = ‘{ "age": 30 }'''; // incomplete data

    undefinedundefined

    try { user = JSON.parse(json); // <- forgot to put "let" before user

    undefinedundefined

    // … } catch (err) { alert("JSON Error:" + err); // JSON Error: ReferenceError: user is not defined // (no JSON Error actually) }

    undefinedundefined

    Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades - suddenly a bug may be discovered that leads to terrible hacks.

    undefinedundefined

    In our case, undefinedundefinedtry...catch is placed to catch "incorrect data" errors. But by its nature, undefinedundefinedcatch gets undefinedundefinedall errors from undefinedundefinedtry. Here it gets an unexpected error, but still shows the same undefinedundefined"JSON Error" message. That's wrong and also makes the code more difficult to debug.undefinedundefined

    undefinedundefined

    To avoid such problems, we can employ the "rethrowing" technique. The rule is simple:

    undefinedundefined

    undefinedundefinedCatch should only process errors that it knows and "rethrow" all others.undefinedundefined

    undefinedundefined

    The "rethrowing" technique can be explained in more detail as:

    undefinedundefined
      undefinedundefined
    1. Catch gets all errors.
    2. undefinedundefined
    3. In the undefinedundefinedcatch (err) {...} block we analyze the error object undefinedundefinederr.undefinedundefined
    4. undefinedundefined
    5. If we don't know how to handle it, we do undefinedundefinedthrow err.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Usually, we can check the error type using the undefinedundefinedinstanceof operator:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run try { user = { /*...*/ }; } catch (err) { *!* if (err instanceof ReferenceError) { */!* alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable } }undefinedundefined

    undefinedundefined

    We can also get the error class name from undefinedundefinederr.name property. All native errors have it. Another option is to read undefinedundefinederr.constructor.name.undefinedundefined

    undefinedundefined

    In the code below, we use rethrowing so that undefinedundefinedcatch only handles undefinedundefinedSyntaxError:undefinedundefined

    undefinedundefined

    run let json = ‘{ "age": 30 }'''; // incomplete data try {

    undefinedundefined

    let user = JSON.parse(json);

    undefinedundefined

    if (!user.name) { throw new SyntaxError("Incomplete data: no name"); }

    undefinedundefined

    undefinedundefined! blabla(); // unexpected error undefinedundefined/!undefinedundefined

    undefinedundefined

    alert( user.name );

    undefinedundefined

    } catch (err) {

    undefinedundefined

    undefinedundefined! if (err instanceof SyntaxError) { alert( "JSON Error:" + err.message ); } else { throw err; // rethrow (undefinedundefined) } /!*undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    The error throwing on line undefinedundefined(*) from inside undefinedundefinedcatch block "falls out" of undefinedundefinedtry...catch and can be either caught by an outer undefinedundefinedtry...catch construct (if it exists), or it kills the script.undefinedundefined

    undefinedundefined

    So the undefinedundefinedcatch block actually handles only errors that it knows how to deal with and "skips" all others.undefinedundefined

    undefinedundefined

    The example below demonstrates how such errors can be caught by one more level of undefinedundefinedtry...catch:undefinedundefined

    undefinedundefined

    run function readData() { let json = ‘{ "age": 30 }''';

    undefinedundefined

    try { // … undefinedundefined! blabla(); // error! undefinedundefined/! } catch (err) { // … if (!(err instanceof SyntaxError)) { undefinedundefined! throw err; // rethrow (don't know how to deal with it) undefinedundefined/! } } }undefinedundefined

    undefinedundefined

    try { readData(); } catch (err) { undefinedundefined! alert( "External catch got:" + err ); // caught it! undefinedundefined/! } undefinedundefined

    undefinedundefined

    Here undefinedundefinedreadData only knows how to handle undefinedundefinedSyntaxError, while the outer undefinedundefinedtry...catch knows how to handle everything.undefinedundefined

    undefinedundefined

    try…catch…finally

    undefinedundefined

    Wait, that's not all.

    undefinedundefined

    The undefinedundefinedtry...catch construct may have one more code clause: undefinedundefinedfinally.undefinedundefined

    undefinedundefined

    If it exists, it runs in all cases:

    undefinedundefined
      undefinedundefined
    • after undefinedundefinedtry, if there were no errors,undefinedundefined
    • undefinedundefined
    • after undefinedundefinedcatch, if there were errors.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The extended syntax looks like this:

    undefinedundefinedundefinedundefined

    Try running this code:

    undefinedundefined

    undefinedundefinedjs run try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); } catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); }undefinedundefined

    undefinedundefined

    The code has two ways of execution:

    undefinedundefined
      undefinedundefined
    1. If you answer "Yes" to "Make an error?", then undefinedundefinedtry -> catch -> finally.undefinedundefined
    2. undefinedundefined
    3. If you say "No", then undefinedundefinedtry -> finally.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    The undefinedundefinedfinally clause is often used when we start doing something and want to finalize it in any case of outcome.undefinedundefined

    undefinedundefined

    For instance, we want to measure the time that a Fibonacci numbers function undefinedundefinedfib(n) takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there's an error during the function call? In particular, the implementation of undefinedundefinedfib(n) in the code below returns an error for negative or non-integer numbers.undefinedundefined

    undefinedundefined

    The undefinedundefinedfinally clause is a great place to finish the measurements no matter what.undefinedundefined

    undefinedundefined

    Here undefinedundefinedfinally guarantees that the time will be measured correctly in both situations - in case of a successful execution of undefinedundefinedfib and in case of an error in it:undefinedundefined

    undefinedundefined

    run let num = +prompt("Enter a positive integer number?", 35)

    undefinedundefined

    let diff, result;

    undefinedundefined

    function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("Must not be negative, and also an integer."); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); }

    undefinedundefined

    let start = Date.now();

    undefinedundefined

    try { result = fib(num); } catch (err) { result = 0; undefinedundefined! } finally { diff = Date.now() - start; } undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(result || "error occurred");

    undefinedundefined

    alert( undefinedundefinedexecution took ${diff}ms ); undefinedundefined

    undefinedundefined

    You can check by running the code with entering undefinedundefined35 into undefinedundefinedprompt - it executes normally, undefinedundefinedfinally after undefinedundefinedtry. And then enter undefinedundefined-1 - there will be an immediate error, and the execution will take undefinedundefined0ms. Both measurements are done correctly.undefinedundefined

    undefinedundefined

    In other words, the function may finish with undefinedundefinedreturn or undefinedundefinedthrow, that doesn't matter. The undefinedundefinedfinally clause executes in both cases.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Variables are local insidetry…catch…finallyundefinedundefined" Please note thatresultundefinedundefinedanddiffundefinedundefinedvariables in the code above are declared *before*try…catch`.undefinedundefined

    undefinedundefined

    Otherwise, if we declared undefinedundefinedlet in undefinedundefinedtry block, it would only be visible inside of it. undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="finallyundefinedundefinedandreturnundefinedundefined" Thefinallyundefinedundefinedclause works for *any* exit fromtry…catchundefinedundefined. That includes an explicitreturn`.undefinedundefined

    undefinedundefined

    In the example below, there's a undefinedundefinedreturn in undefinedundefinedtry. In this case, undefinedundefinedfinally is executed just before the control returns to the outer code.undefinedundefined

    undefinedundefined

    run function func() {

    undefinedundefined

    try { undefinedundefined! return 1; undefinedundefined/!undefinedundefined

    undefinedundefined

    } catch (err) { /* … undefinedundefined/ } finally { !undefinedundefined alert( ‘finally' ); /!* } }undefinedundefined

    undefinedundefined

    alert( func() ); // first works alert from finally, and then this one

    undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    undefinedundefinedsmart header="try…finally`"undefinedundefined

    undefinedundefined

    The undefinedundefinedtry...finally construct, without undefinedundefinedcatch clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized.undefinedundefined

    undefinedundefinedundefinedundefined

    In the code above, an error inside undefinedundefinedtry always falls out, because there's no undefinedundefinedcatch. But undefinedundefinedfinally works before the execution flow leaves the function. undefinedundefined

    undefinedundefined

    Global catch

    undefinedundefined

    undefinedundefinedwarn header="Environment-specific" The information from this section is not a part of the core JavaScript.undefinedundefined

    undefinedundefined

    Let's imagine we've got a fatal error outside of undefinedundefinedtry...catch, and the script died. Like a programming error or some other terrible thing.undefinedundefined

    undefinedundefined

    Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc.

    undefinedundefined

    There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has undefinedundefinedundefinedundefinedprocess.on("uncaughtException")undefinedundefined for that. And in the browser we can assign a function to the special undefinedundefinedwindow.onerror property, that will run in case of an uncaught error.undefinedundefined

    undefinedundefined

    The syntax:

    undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefinedmessageundefinedundefined
    undefinedundefined
    Error message.
    undefinedundefined
    undefinedundefinedurlundefinedundefined
    undefinedundefined
    URL of the script where error happened.
    undefinedundefined
    undefinedundefinedline, undefinedundefinedcolundefinedundefined
    undefinedundefined
    Line and column numbers where error happened.
    undefinedundefined
    undefinedundefinederrorundefinedundefined
    undefinedundefined
    Error object.
    undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefined

    ``undefinedundefinedhtml run untrusted refresh height=1 <script> *!* window.onerror = function(message, url, line, col, error) { alert(${message}At undefinedundefinedundefinedundefinedlundefinedundefinediundefinedundefinednundefinedundefinede:undefinedundefined{col} of ${url}`); }; undefinedundefined/!undefinedundefined

    undefinedundefined

    function readData() { badFunc(); // Whoops, something went wrong! }

    readData(); undefinedundefinedundefinedundefined

    undefinedundefined

    The role of the global handler undefinedundefinedwindow.onerror is usually not to recover the script execution - that's probably impossible in case of programming errors, but to send the error message to developers.undefinedundefined

    undefinedundefined

    There are also web-services that provide error-logging for such cases, like undefinedundefinedhttps://errorception.com or undefinedundefinedhttp://www.muscula.com.undefinedundefined

    undefinedundefined

    They work like this:

    undefinedundefined
      undefinedundefined
    1. We register at the service and get a piece of JS (or a script URL) from them to insert on pages.
    2. undefinedundefined
    3. That JS script sets a custom undefinedundefinedwindow.onerror function.undefinedundefined
    4. undefinedundefined
    5. When an error occurs, it sends a network request about it to the service.
    6. undefinedundefined
    7. We can log in to the service web interface and see errors.
    8. undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    The undefinedundefinedtry...catch construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    There may be no undefinedundefinedcatch section or no undefinedundefinedfinally, so shorter constructs undefinedundefinedtry...catch and undefinedundefinedtry...finally are also valid.undefinedundefined

    undefinedundefined

    Error objects have following properties:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedmessage - the human-readable error message.undefinedundefined
    • undefinedundefined
    • undefinedundefinedname - the string with error name (error constructor name).undefinedundefined
    • undefinedundefined
    • undefinedundefinedstack (non-standard, but well-supported) - the stack at the moment of error creation.undefinedundefined
    • undefinedundefined
    undefinedundefined

    If an error object is not needed, we can omit it by using undefinedundefinedcatch { instead of undefinedundefinedcatch (err) {.undefinedundefined

    undefinedundefined

    We can also generate our own errors using the undefinedundefinedthrow operator. Technically, the argument of undefinedundefinedthrow can be anything, but usually it's an error object inheriting from the built-in undefinedundefinedError class. More on extending errors in the next chapter.undefinedundefined

    undefinedundefined

    undefinedundefinedRethrowing is a very important pattern of error handling: a undefinedundefinedcatch block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know.undefinedundefined

    undefinedundefined

    Even if we don't have undefinedundefinedtry...catch, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's undefinedundefinedwindow.onerror.undefinedundefined

    undefinedundefined

    Custom errors, extending Error

    undefinedundefined

    When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need undefinedundefinedHttpError, for database operations undefinedundefinedDbError, for searching operations undefinedundefinedNotFoundError and so on.undefinedundefined

    undefinedundefined

    Our errors should support basic error properties like undefinedundefinedmessage, undefinedundefinedname and, preferably, undefinedundefinedstack. But they also may have other properties of their own, e.g. undefinedundefinedHttpError objects may have a undefinedundefinedstatusCode property with a value like undefinedundefined404 or undefinedundefined403 or undefinedundefined500.undefinedundefined

    undefinedundefined

    JavaScript allows to use undefinedundefinedthrow with any argument, so technically our custom error classes don't need to inherit from undefinedundefinedError. But if we inherit, then it becomes possible to use undefinedundefinedobj instanceof Error to identify error objects. So it's better to inherit from it.undefinedundefined

    undefinedundefined

    As the application grows, our own errors naturally form a hierarchy. For instance, undefinedundefinedHttpTimeoutError may inherit from undefinedundefinedHttpError, and so on.undefinedundefined

    undefinedundefined

    Extending Error

    undefinedundefined

    As an example, let's consider a function undefinedundefinedreadUser(json) that should read JSON with user data.undefinedundefined

    undefinedundefined

    Here's an example of how a valid undefinedundefinedjson may look:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet json undefinedundefined=undefinedundefined`{ "name": "John", "age": 30 }`undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Internally, we'll use undefinedundefinedJSON.parse. If it receives malformed undefinedundefinedjson, then it throws undefinedundefinedSyntaxError. But even if undefinedundefinedjson is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have undefinedundefinedname and undefinedundefinedage properties that are essential for our users.undefinedundefined

    undefinedundefined

    Our function undefinedundefinedreadUser(json) will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a undefinedundefinedSyntaxError, because the data is syntactically correct, but another kind of error. We'll call it undefinedundefinedValidationError and create a class for it. An error of that kind should also carry the information about the offending field.undefinedundefined

    undefinedundefined

    Our undefinedundefinedValidationError class should inherit from the built-in undefinedundefinedError class.undefinedundefined

    undefinedundefined

    That class is built-in, but here's its approximate code so we can understand what we're extending:

    undefinedundefinedundefinedundefined

    Now let's inherit undefinedundefinedValidationError from it and try it in action:undefinedundefined

    undefinedundefined

    run untrusted undefinedundefined! class ValidationError extends Error { undefinedundefined/! constructor(message) { super(message); // (1) this.name = "ValidationError"; // (2) } }undefinedundefined

    undefinedundefined

    function test() { throw new ValidationError("Whoops!"); }

    undefinedundefined

    try { test(); } catch(err) { alert(err.message); // Whoops! alert(err.name); // ValidationError alert(err.stack); // a list of nested calls with line numbers for each }

    undefinedundefined

    Please note: in the line undefinedundefined(1) we call the parent constructor. JavaScript requires us to call undefinedundefinedsuper in the child constructor, so that's obligatory. The parent constructor sets the undefinedundefinedmessage property.undefinedundefined

    undefinedundefined

    The parent constructor also sets the undefinedundefinedname property to undefinedundefined"Error", so in the line undefinedundefined(2) we reset it to the right value.undefinedundefined

    undefinedundefined

    Let's try to use it in undefinedundefinedreadUser(json):undefinedundefined

    undefinedundefined

    run class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }

    undefinedundefined

    // Usage function readUser(json) { let user = JSON.parse(json);

    undefinedundefined

    if (!user.age) { throw new ValidationError("No field: age"); } if (!user.name) { throw new ValidationError("No field: name"); }

    undefinedundefined

    return user; }

    undefinedundefined

    // Working example with try..catch

    undefinedundefined

    try { let user = readUser(‘{ "age": 25 }'''); } catch (err) { if (err instanceof ValidationError) { undefinedundefined! alert("Invalid data:" + err.message); // Invalid data: No field: name undefinedundefined/! } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error:" + err.message); } else { throw err; // unknown error, rethrow it (**) } } undefinedundefined

    undefinedundefined

    The undefinedundefinedtry..catch block in the code above handles both our undefinedundefinedValidationError and the built-in undefinedundefinedSyntaxError from undefinedundefinedJSON.parse.undefinedundefined

    undefinedundefined

    Please take a look at how we use undefinedundefinedinstanceof to check for the specific error type in the line undefinedundefined(*).undefinedundefined

    undefinedundefined

    We could also look at undefinedundefinederr.name, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    The undefinedundefinedinstanceof version is much better, because in the future we are going to extend undefinedundefinedValidationError, make subtypes of it, like undefinedundefinedPropertyRequiredError. And undefinedundefinedinstanceof check will continue to work for new inheriting classes. So that's future-proof.undefinedundefined

    undefinedundefined

    Also it's important that if undefinedundefinedcatch meets an unknown error, then it rethrows it in the line undefinedundefined(**). The undefinedundefinedcatch block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through.undefinedundefined

    undefinedundefined

    Further inheritance

    undefinedundefined

    The undefinedundefinedValidationError class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for undefinedundefinedage). Let's make a more concrete class undefinedundefinedPropertyRequiredError, exactly for absent properties. It will carry additional information about the property that's missing.undefinedundefined

    undefinedundefined

    run class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }

    undefinedundefined

    undefinedundefined! class PropertyRequiredError extends ValidationError { constructor(property) { super("No property:" + property); this.name = "PropertyRequiredError"; this.property = property; } } undefinedundefined/!undefinedundefined

    undefinedundefined

    // Usage function readUser(json) { let user = JSON.parse(json);

    undefinedundefined

    if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); }

    undefinedundefined

    return user; }

    undefinedundefined

    // Working example with try..catch

    undefinedundefined

    try { let user = readUser(‘{ "age": 25 }'''); } catch (err) { if (err instanceof ValidationError) { undefinedundefined! alert("Invalid data:" + err.message); // Invalid data: No property: name alert(err.name); // PropertyRequiredError alert(err.property); // name undefinedundefined/! } else if (err instanceof SyntaxError) { alert("JSON Syntax Error:" + err.message); } else { throw err; // unknown error, rethrow it } } undefinedundefined

    undefinedundefined

    The new class undefinedundefinedPropertyRequiredError is easy to use: we only need to pass the property name: undefinedundefinednew PropertyRequiredError(property). The human-readable undefinedundefinedmessage is generated by the constructor.undefinedundefined

    undefinedundefined

    Please note that undefinedundefinedthis.name in undefinedundefinedPropertyRequiredError constructor is again assigned manually. That may become a bit tedious - to assign undefinedundefinedthis.name = <class name> in every custom error class. We can avoid it by making our own "basic error" class that assigns undefinedundefinedthis.name = this.constructor.name. And then inherit all our custom errors from it.undefinedundefined

    undefinedundefined

    Let's call it undefinedundefinedMyError.undefinedundefined

    undefinedundefined

    Here's the code with undefinedundefinedMyError and other custom error classes, simplified:undefinedundefined

    undefinedundefined

    run class MyError extends Error { constructor(message) { super(message); undefinedundefined! this.name = this.constructor.name; undefinedundefined/! } }undefinedundefined

    undefinedundefined

    class ValidationError extends MyError { }

    undefinedundefined

    class PropertyRequiredError extends ValidationError { constructor(property) { super("No property:" + property); this.property = property; } }

    undefinedundefined

    // name is correct alert( new PropertyRequiredError("field").name ); // PropertyRequiredError

    undefinedundefined

    Now custom errors are much shorter, especially undefinedundefinedValidationError, as we got rid of the undefinedundefined"this.name = ..." line in the constructor.undefinedundefined

    undefinedundefined

    Wrapping exceptions

    undefinedundefined

    The purpose of the function undefinedundefinedreadUser in the code above is "to read the user data". There may occur different kinds of errors in the process. Right now we have undefinedundefinedSyntaxError and undefinedundefinedValidationError, but in the future undefinedundefinedreadUser function may grow and probably generate other kinds of errors.undefinedundefined

    undefinedundefined

    The code which calls undefinedundefinedreadUser should handle these errors. Right now it uses multiple undefinedundefinedifs in the undefinedundefinedcatch block, that check the class and handle known errors and rethrow the unknown ones.undefinedundefined

    undefinedundefined

    The scheme is like this:

    undefinedundefinedundefinedundefined

    In the code above we can see two types of errors, but there can be more.

    undefinedundefined

    If the undefinedundefinedreadUser function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one every time?undefinedundefined

    undefinedundefined

    Often the answer is "No": we'd like to be "one level above all that". We just want to know if there was a "data reading error" - why exactly it happened is often irrelevant (the error message describes it). Or, even better, we'd like to have a way to get the error details, but only if we need to.

    undefinedundefined

    The technique that we describe here is called "wrapping exceptions".

    undefinedundefined
      undefinedundefined
    1. We'll make a new class undefinedundefinedReadError to represent a generic "data reading" error.undefinedundefined
    2. undefinedundefined
    3. The function undefinedundefinedreadUser will catch data reading errors that occur inside it, such as undefinedundefinedValidationError and undefinedundefinedSyntaxError, and generate a undefinedundefinedReadError instead.undefinedundefined
    4. undefinedundefined
    5. The undefinedundefinedReadError object will keep the reference to the original error in its undefinedundefinedcause property.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Then the code that calls undefinedundefinedreadUser will only have to check for undefinedundefinedReadError, not for every kind of data reading errors. And if it needs more details of an error, it can check its undefinedundefinedcause property.undefinedundefined

    undefinedundefined

    Here's the code that defines undefinedundefinedReadError and demonstrates its use in undefinedundefinedreadUser and undefinedundefinedtry..catch:undefinedundefined

    undefinedundefined

    run class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = ‘ReadError'; } }

    undefinedundefined

    class ValidationError extends Error { /undefinedundefined/ } class PropertyRequiredError extends ValidationError { /* … */ }undefinedundefined

    undefinedundefined

    function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); }

    undefinedundefined

    if (!user.name) { throw new PropertyRequiredError("name"); } }

    undefinedundefined

    function readUser(json) { let user;

    undefinedundefined

    try { user = JSON.parse(json); } catch (err) { undefinedundefined! if (err instanceof SyntaxError) { throw new ReadError("Syntax Error", err); } else { throw err; } undefinedundefined/! }undefinedundefined

    undefinedundefined

    try { validateUser(user); } catch (err) { undefinedundefined! if (err instanceof ValidationError) { throw new ReadError("Validation Error", err); } else { throw err; } undefinedundefined/! }undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    try { readUser(‘{bad json}'''); } catch (e) { if (e instanceof ReadError) { undefinedundefined! alert(e); // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error:" + e.cause); undefinedundefined/! } else { throw e; } } undefinedundefined

    undefinedundefined

    In the code above, undefinedundefinedreadUser works exactly as described - catches syntax and validation errors and throws undefinedundefinedReadError errors instead (unknown errors are rethrown as usual).undefinedundefined

    undefinedundefined

    So the outer code checks undefinedundefinedinstanceof ReadError and that's it. No need to list all possible error types.undefinedundefined

    undefinedundefined

    The approach is called "wrapping exceptions", because we take "low level" exceptions and "wrap" them into undefinedundefinedReadError that is more abstract. It is widely used in object-oriented programming.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • We can inherit from undefinedundefinedError and other built-in error classes normally. We just need to take care of the undefinedundefinedname property and don't forget to call undefinedundefinedsuper.undefinedundefined
    • undefinedundefined
    • We can use undefinedundefinedinstanceof to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from a 3rd-party library and there's no easy way to get its class. Then undefinedundefinedname property can be used for such checks.undefinedundefined
    • undefinedundefined
    • Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like undefinedundefinederr.cause in the examples above, but that's not strictly required.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Introduction: callbacks

    undefinedundefined

    warn header="We use browser methods in examples here" To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations.

    undefinedundefined

    If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the undefinedundefinednext part of the tutorial.undefinedundefined

    undefinedundefined

    Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise.

    undefinedundefined

    Many functions are provided by JavaScript host environments that allow you to schedule undefinedundefinedasynchronous actions. In other words, actions that we initiate now, but they finish later.undefinedundefined

    undefinedundefined

    For instance, one such function is the undefinedundefinedsetTimeout function.undefinedundefined

    undefinedundefined

    There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters).

    undefinedundefined

    Take a look at the function undefinedundefinedloadScript(src), that loads a script with the given undefinedundefinedsrc:undefinedundefined

    undefinedundefinedundefinedundefined

    It appends to the document the new, dynamically created, tag undefinedundefined<script src="…"> with given undefinedundefinedsrc. The browser automatically starts loading it and executes when complete.undefinedundefined

    undefinedundefined

    We can use this function like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// load and execute the script at the given pathundefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'/my/script.js')undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The script is executed "asynchronously", as it starts loading now, but runs later, when the function has already finished.

    undefinedundefined

    If there's any code below undefinedundefinedloadScript(…), it doesn't wait until the script loading finishes.undefinedundefined

    undefinedundefinedundefinedundefined

    Let's say we need to use the new script as soon as it loads. It declares new functions, and we want to run them.

    undefinedundefined

    But if we do that immediately after the undefinedundefinedloadScript(…) call, that wouldn't work:undefinedundefined

    undefinedundefinedundefinedundefined

    Naturally, the browser probably didn't have time to load the script. As of now, the undefinedundefinedloadScript function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script.undefinedundefined

    undefinedundefined

    Let's add a undefinedundefinedcallback function as a second argument to undefinedundefinedloadScript that should execute when the script loads:undefinedundefined

    undefinedundefinedundefinedundefined

    Now if we want to call new functions from the script, we should write that in the callback:

    undefinedundefinedundefinedundefined

    That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.

    undefinedundefined

    Here's a runnable example with a real script:

    undefinedundefined

    run function loadScript(src, callback) { let script = document.createElement(‘script'); script.src = src; script.onload = () => callback(script); document.head.append(script); }

    undefinedundefined

    undefinedundefined! loadScript(‘https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(undefinedundefinedCool, the script ${script.src} is loaded); alert( _ ); // function declared in the loaded script }); undefinedundefined/! undefinedundefined

    undefinedundefined

    That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a undefinedundefinedcallback argument where we put the function to run after it's complete.undefinedundefined

    undefinedundefined

    Here we did it in undefinedundefinedloadScript, but of course it's a general approach.undefinedundefined

    undefinedundefined

    Callback in callback

    undefinedundefined

    How can we load two scripts sequentially: the first one, and then the second one after it?

    undefinedundefined

    The natural solution would be to put the second undefinedundefinedloadScript call inside the callback, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    After the outer undefinedundefinedloadScript is complete, the callback initiates the inner one.undefinedundefined

    undefinedundefined

    What if we want one more script…?

    undefinedundefinedundefinedundefined

    So, every new action is inside a callback. That's fine for few actions, but not good for many, so we'll see other variants soon.

    undefinedundefined

    Handling errors

    undefinedundefined

    In the above examples we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.

    undefinedundefined

    Here's an improved version of undefinedundefinedloadScript that tracks loading errors:undefinedundefined

    undefinedundefinedundefinedundefined

    It calls undefinedundefinedcallback(null, script) for successful load and undefinedundefinedcallback(error) otherwise.undefinedundefined

    undefinedundefined

    The usage:

    undefinedundefinedundefinedundefined

    Once again, the recipe that we used for undefinedundefinedloadScript is actually quite common. It's called the "error-first callback" style.undefinedundefined

    undefinedundefined

    The convention is: 1. The first argument of the undefinedundefinedcallback is reserved for an error if it occurs. Then undefinedundefinedcallback(err) is called. 2. The second argument (and the next ones if needed) are for the successful result. Then undefinedundefinedcallback(null, result1, result2…) is called.undefinedundefined

    undefinedundefined

    So the single undefinedundefinedcallback function is used both for reporting errors and passing back results.undefinedundefined

    undefinedundefined

    Pyramid of Doom

    undefinedundefined

    From the first look, it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine.

    undefinedundefined

    But for multiple asynchronous actions that follow one after another we'll have code like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'1.js'undefinedundefined,undefinedundefinedfunction(errorundefinedundefined, script) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedif (error) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedhandleError(error)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'2.js'undefinedundefined,undefinedundefinedfunction(errorundefinedundefined, script) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedif (error) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedhandleError(error)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'3.js'undefinedundefined,undefinedundefinedfunction(errorundefinedundefined, script) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedif (error) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedhandleError(error)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefined// ...continue after all scripts are loaded (*)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined          }undefinedundefinedundefinedundefinedundefinedundefined        }undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined      }undefinedundefinedundefinedundefinedundefinedundefined    }undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    In the code above: 1. We load undefinedundefined1.js, then if there's no error. 2. We load undefinedundefined2.js, then if there's no error. 3. We load undefinedundefined3.js, then if there's no error - do something else undefinedundefined(*).undefinedundefined

    undefinedundefined

    As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have real code instead of undefinedundefined... that may include more loops, conditional statements and so on.undefinedundefined

    undefinedundefined

    That's sometimes called "callback hell" or "pyramid of doom."

    undefinedundefined undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control.

    undefinedundefined

    So this way of coding isn't very good.

    undefinedundefined

    We can try to alleviate the problem by making every action a standalone function, like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'1.js'undefinedundefined, step1)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedstep1(errorundefinedundefined, script) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedif (error) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedhandleError(error)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'2.js'undefinedundefined, step2)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedstep2(errorundefinedundefined, script) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedif (error) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedhandleError(error)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefinedloadScript(undefinedundefined'3.js'undefinedundefined, step3)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedstep3(errorundefinedundefined, script) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedif (error) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedhandleError(error)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...continue after all scripts are loaded (*)undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    See? It does the same, and there's no deep nesting now because we made every action a separate top-level function.

    undefinedundefined

    It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that one needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump.

    undefinedundefined

    Also, the functions named undefinedundefinedstep* are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of namespace cluttering here.undefinedundefined

    undefinedundefined

    We'd like to have something better.

    undefinedundefined

    Luckily, there are other ways to avoid such pyramids. One of the best ways is to use "promises," described in the next chapter.

    undefinedundefined

    Promise

    undefinedundefined

    Imagine that you're a top singer, and fans ask day and night for your upcoming song.

    undefinedundefined

    To get some relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, a fire in the studio, so that you can't publish the song, they will still be notified.

    undefinedundefined

    Everyone is happy: you, because the people don't crowd you anymore, and fans, because they won't miss the song.

    undefinedundefined

    This is a real-life analogy for things we often have in programming:

    undefinedundefined
      undefinedundefined
    1. A "producing code" that does something and takes time. For instance, some code that loads the data over a network. That's a "singer".
    2. undefinedundefined
    3. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans".
    4. undefinedundefined
    5. A undefinedundefinedpromise is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    The analogy isn't terribly accurate, because JavaScript promises are more complex than a simple subscription list: they have additional features and limitations. But it's fine to begin with.

    undefinedundefined

    The constructor syntax for a promise object is:

    undefinedundefinedundefinedundefined

    The function passed to undefinedundefinednew Promise is called the undefinedundefinedexecutor. When undefinedundefinednew Promise is created, the executor runs automatically. It contains the producing code which should eventually produce the result. In terms of the analogy above: the executor is the "singer".undefinedundefined

    undefinedundefined

    Its arguments undefinedundefinedresolve and undefinedundefinedreject are callbacks provided by JavaScript itself. Our code is only inside the executor.undefinedundefined

    undefinedundefined

    When the executor obtains the result, be it soon or late, doesn't matter, it should call one of these callbacks:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedresolve(value) — if the job is finished successfully, with result undefinedundefinedvalue.undefinedundefined
    • undefinedundefined
    • undefinedundefinedreject(error) — if an error has occurred, undefinedundefinederror is the error object.undefinedundefined
    • undefinedundefined
    undefinedundefined

    So to summarize: the executor runs automatically and attempts to perform a job. When it is finished with the attempt, it calls undefinedundefinedresolve if it was successful or undefinedundefinedreject if there was an error.undefinedundefined

    undefinedundefined

    The undefinedundefinedpromise object returned by the undefinedundefinednew Promise constructor has these internal properties:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedstate — initially undefinedundefined"pending", then changes to either undefinedundefined"fulfilled" when undefinedundefinedresolve is called or undefinedundefined"rejected" when undefinedundefinedreject is called.undefinedundefined
    • undefinedundefined
    • undefinedundefinedresult — initially undefinedundefinedundefined, then changes to undefinedundefinedvalue when undefinedundefinedresolve(value) called or undefinedundefinederror when undefinedundefinedreject(error) is called.undefinedundefined
    • undefinedundefined
    undefinedundefined

    So the executor eventually moves undefinedundefinedpromise to one of these states:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Later we'll see how "fans" can subscribe to these changes.

    undefinedundefined

    Here's an example of a promise constructor and a simple executor function with "producing code" that takes time (via undefinedundefinedsetTimeout):undefinedundefined

    undefinedundefined

    run let promise = new Promise(function(resolve, reject) { // the function is executed automatically when the promise is constructed

    undefinedundefined

    // after 1 second signal that the job is done with the result "done" setTimeout(() => undefinedundefined!resolve("done")undefinedundefined/!, 1000); }); undefinedundefined

    undefinedundefined

    We can see two things by running the code above:

    undefinedundefined
      undefinedundefined
    1. The executor is called automatically and immediately (by undefinedundefinednew Promise).undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      The executor receives two arguments: undefinedundefinedresolve and undefinedundefinedreject. These functions are pre-defined by the JavaScript engine, so we don't need to create them. We should only call one of them when ready.undefinedundefined

      undefinedundefined

      After one second of "processing" the executor calls undefinedundefinedresolve("done") to produce the result. This changes the state of the undefinedundefinedpromise object:undefinedundefined

      undefinedundefined

      undefinedundefinedundefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    That was an example of a successful job completion, a "fulfilled promise".

    undefinedundefined

    And now an example of the executor rejecting the promise with an error:

    undefinedundefinedundefinedundefined

    The call to undefinedundefinedreject(...) moves the promise object to undefinedundefined"rejected" state:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    To summarize, the executor should perform a job (usually something that takes time) and then call undefinedundefinedresolve or undefinedundefinedreject to change the state of the corresponding promise object.undefinedundefined

    undefinedundefined

    A promise that is either resolved or rejected is called "settled", as opposed to an initially "pending" promise.

    undefinedundefined

    undefinedundefinedsmart header="There can be only a single result or an error" The executor should call only oneresolveundefinedundefinedor onereject`. Any state change is final.undefinedundefined

    undefinedundefined

    All further calls of undefinedundefinedresolve and undefinedundefinedreject are ignored:undefinedundefined

    undefinedundefinedundefinedundefined

    The idea is that a job done by the executor may have only one result or an error.

    undefinedundefined

    Also, undefinedundefinedresolve/undefinedundefinedreject expect only one argument (or none) and will ignore additional arguments. undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Reject with `Error` objects" In case something goes wrong, the executor should call `reject`. That can be done with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Immediately callingresolveundefinedundefined/rejectundefinedundefined" In practice, an executor usually does something asynchronously and callsresolveundefinedundefined/rejectundefinedundefinedafter some time, but it doesn't have to. We also can callresolveundefinedundefinedorreject` immediately, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    For instance, this might happen when we start to do a job but then see that everything has already been completed and cached.

    undefinedundefined

    That's fine. We immediately have a resolved promise.

    undefinedundefined

    undefinedundefinedsmart header="The `state` and `result` are internal" The properties `state` and `result` of the Promise object are internal. We can't directly access them. We can use the methods `.then`/`.catch`/`.finally` for that. They are described below.undefinedundefined

    undefinedundefined

    Consumers: then, catch, finally

    undefinedundefined

    A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using methods undefinedundefined.then, undefinedundefined.catch and undefinedundefined.finally.undefinedundefined

    undefinedundefined

    then

    undefinedundefined

    The most important, fundamental one is undefinedundefined.then.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    The first argument of undefinedundefined.then is a function that runs when the promise is resolved, and receives the result.undefinedundefined

    undefinedundefined

    The second argument of undefinedundefined.then is a function that runs when the promise is rejected, and receives the error.undefinedundefined

    undefinedundefined

    For instance, here's a reaction to a successfully resolved promise:

    undefinedundefined

    run let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); });

    undefinedundefined

    // resolve runs the first function in .then promise.then( undefinedundefined! result => alert(result), // shows "done!" after 1 second undefinedundefined/! error => alert(error) // doesn't run ); undefinedundefined

    undefinedundefined

    The first function was executed.

    undefinedundefined

    And in the case of a rejection, the second one:

    undefinedundefined

    run let promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("Whoops!")), 1000); });

    undefinedundefined

    // reject runs the second function in .then promise.then( result => alert(result), // doesn't run undefinedundefined! error => alert(error) // shows "Error: Whoops!" after 1 second undefinedundefined/! ); undefinedundefined

    undefinedundefined

    If we're interested only in successful completions, then we can provide only one function argument to undefinedundefined.then:undefinedundefined

    undefinedundefined

    run let promise = new Promise(resolve => { setTimeout(() => resolve("done!"), 1000); });

    undefinedundefined

    undefinedundefined! promise.then(alert); // shows "done!" after 1 second undefinedundefined/! undefinedundefined

    undefinedundefined

    catch

    undefinedundefined

    If we're interested only in errors, then we can use undefinedundefinednull as the first argument: undefinedundefined.then(null, errorHandlingFunction). Or we can use undefinedundefined.catch(errorHandlingFunction), which is exactly the same:undefinedundefined

    undefinedundefined

    run let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); });

    undefinedundefined

    undefinedundefined! // .catch(f) is the same as promise.then(null, f) promise.catch(alert); // shows "Error: Whoops!" after 1 second undefinedundefined/! undefinedundefined

    undefinedundefined

    The call undefinedundefined.catch(f) is a complete analog of undefinedundefined.then(null, f), it's just a shorthand.undefinedundefined

    undefinedundefined

    finally

    undefinedundefined

    Just like there's a undefinedundefinedfinally clause in a regular undefinedundefinedtry {...} catch {...}, there's undefinedundefinedfinally in promises.undefinedundefined

    undefinedundefined

    The call undefinedundefined.finally(f) is similar to undefinedundefined.then(f, f) in the sense that undefinedundefinedf always runs when the promise is settled: be it resolve or reject.undefinedundefined

    undefinedundefined

    undefinedundefinedfinally is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed anymore, no matter what the outcome is.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefinedundefinedundefined

    That said, undefinedundefinedfinally(f) isn't exactly an alias of undefinedundefinedthen(f,f) though. There are few subtle differences:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. A undefinedundefinedfinally handler has no arguments. In undefinedundefinedfinally we don't know whether the promise is successful or not. That's all right, as our task is usually to perform "general" finalizing procedures.undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      A undefinedundefinedfinally handler passes through results and errors to the next handler.undefinedundefined

      undefinedundefined

      For instance, here the result is passed through undefinedundefinedfinally to undefinedundefinedthen: undefinedundefinedjs run new Promise((resolve, reject) => { setTimeout(() => resolve("result"), 2000) }) .finally(() => alert("Promise ready")) .then(result => alert(result)); // <-- .then handles the resultundefinedundefined

      undefinedundefined

      And here there's an error in the promise, passed through undefinedundefinedfinally to undefinedundefinedcatch:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) .catch(err => alert(err)); // <-- .catch handles the error objectundefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    That's very convenient, because undefinedundefinedfinally is not meant to process a promise result. So it passes it through.undefinedundefined

    undefinedundefined

    We'll talk more about promise chaining and result-passing between handlers in the next chapter.

    undefinedundefined

    undefinedundefinedsmart header="We can attach handlers to settled promises" If a promise is pending,.then/catch/finally` handlers wait for it. Otherwise, if a promise has already settled, they just run:undefinedundefined

    undefinedundefined

    run // the promise becomes resolved immediately upon creation let promise = new Promise(resolve => resolve("done!"));

    undefinedundefined

    promise.then(alert); // done! (shows up right now)

    undefinedundefined
    undefinedundefined
    Note that this makes promises more powerful than the real life "subscription list" scenario. If the singer has already released their song and then a person signs up on the subscription list, they probably won't receive that song. Subscriptions in real life must be done prior to the event.
    Promises are more flexible. We can add handlers any time: if the result is already there, they just execute.undefinedundefined
    undefinedundefined

    Next, let's see more practical examples of how promises can help us write asynchronous code.

    undefinedundefined

    Example: loadScript [#loadscript]

    undefinedundefined

    We've got the undefinedundefinedloadScript function for loading a script from the previous chapter.undefinedundefined

    undefinedundefined

    Here's the callback-based variant, just to remind us of it:

    undefinedundefinedundefinedundefined

    Let's rewrite it using Promises.

    undefinedundefined

    The new function undefinedundefinedloadScript will not require a callback. Instead, it will create and return a Promise object that resolves when the loading is complete. The outer code can add handlers (subscribing functions) to it using undefinedundefined.then:undefinedundefined

    undefinedundefined

    run function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement(‘script'); script.src = src;

    undefinedundefined
    undefinedundefinedscript.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));
    document.head.append(script);undefinedundefined
    undefinedundefined

    }); }

    undefinedundefined

    Usage:

    undefinedundefined

    run let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

    undefinedundefined

    promise.then( script => alert(undefinedundefined${script.src} is loaded!), error => alert(undefinedundefinedError: ${error.message}) );undefinedundefined

    undefinedundefined

    promise.then(script => alert(‘Another handler…'));

    undefinedundefined

    We can immediately see a few benefits over the callback-based pattern:

    undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    PromisesCallbacks
    Promises allow us to do things in the natural order. First, we run undefinedundefinedloadScript(script), and undefinedundefined.then we write what to do with the result.undefinedundefinedWe must have a undefinedundefinedcallback function at our disposal when calling undefinedundefinedloadScript(script, callback). In other words, we must know what to do with the result undefinedundefinedbeforeundefinedundefinedloadScript is called.undefinedundefined
    We can call undefinedundefined.then on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next chapter: undefinedundefined.undefinedundefinedThere can be only one callback.
    undefinedundefined

    So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.

    undefinedundefined

    Promises chaining

    undefinedundefined

    Let's return to the problem mentioned in the chapter undefinedundefinedinfo:callbacks: we have a sequence of asynchronous tasks to be performed one after another — for instance, loading scripts. How can we code it well?undefinedundefined

    undefinedundefined

    Promises provide a couple of recipes to do that.

    undefinedundefined

    In this chapter we cover promise chaining.

    undefinedundefined

    It looks like this:

    undefinedundefined

    run new Promise(function(resolve, reject) {

    undefinedundefined

    setTimeout(() => resolve(1), 1000); // (*)

    undefinedundefined

    }).then(function(result) { // (**)

    undefinedundefined

    alert(result); // 1 return result * 2;

    undefinedundefined

    }).then(function(result) { // (***)

    undefinedundefined

    alert(result); // 2 return result * 2;

    undefinedundefined

    }).then(function(result) {

    undefinedundefined

    alert(result); // 4 return result * 2;

    undefinedundefined

    });

    undefinedundefined

    The idea is that the result is passed through the chain of undefinedundefined.then handlers.undefinedundefined

    undefinedundefined

    Here the flow is: 1. The initial promise resolves in 1 second undefinedundefined(*), 2. Then the undefinedundefined.then handler is called undefinedundefined(**). 3. The value that it returns is passed to the next undefinedundefined.then handler undefinedundefined(***) 4. …and so on.undefinedundefined

    undefinedundefined

    As the result is passed along the chain of handlers, we can see a sequence of undefinedundefinedalert calls: undefinedundefined1 -> undefinedundefined2 -> undefinedundefined4.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The whole thing works, because a call to undefinedundefinedpromise.then returns a promise, so that we can call the next undefinedundefined.then on it.undefinedundefined

    undefinedundefined

    When a handler returns a value, it becomes the result of that promise, so the next undefinedundefined.then is called with it.undefinedundefined

    undefinedundefined

    undefinedundefinedA classic newbie error: technically we can also add many undefinedundefined.then to a single promise. This is not chaining.undefinedundefinedundefinedundefined

    undefinedundefined

    For example: run let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); });

    undefinedundefined

    promise.then(function(result) { alert(result); // 1 return result * 2; });

    undefinedundefined

    promise.then(function(result) { alert(result); // 1 return result * 2; });

    undefinedundefined

    promise.then(function(result) { alert(result); // 1 return result * 2; });

    undefinedundefined

    What we did here is just several handlers to one promise. They don't pass the result to each other; instead they process it independently.

    undefinedundefined

    Here's the picture (compare it with the chaining above):

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    All undefinedundefined.then on the same promise get the same result - the result of that promise. So in the code above all undefinedundefinedalert show the same: undefinedundefined1.undefinedundefined

    undefinedundefined

    In practice we rarely need multiple handlers for one promise. Chaining is used much more often.

    undefinedundefined

    Returning promises

    undefinedundefined

    A handler, used in undefinedundefined.then(handler) may create and return a promise.undefinedundefined

    undefinedundefined

    In that case further handlers wait until it settles, and then get its result.

    undefinedundefined

    For instance:

    undefinedundefined

    run new Promise(function(resolve, reject) {

    undefinedundefined

    setTimeout(() => resolve(1), 1000);

    undefinedundefined

    }).then(function(result) {

    undefinedundefined

    alert(result); // 1

    undefinedundefined

    undefinedundefined! return new Promise((resolve, reject) => { // (undefinedundefined) setTimeout(() => resolve(result 2), 1000); }); undefinedundefined/!undefinedundefined

    undefinedundefined

    }).then(function(result) { // (**)

    undefinedundefined

    alert(result); // 2

    undefinedundefined

    return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); });

    undefinedundefined

    }).then(function(result) {

    undefinedundefined

    alert(result); // 4

    undefinedundefined

    });

    undefinedundefined

    Here the first undefinedundefined.then shows undefinedundefined1 and returns undefinedundefinednew Promise(…) in the line undefinedundefined(*). After one second it resolves, and the result (the argument of undefinedundefinedresolve, here it's undefinedundefinedresult * 2) is passed on to handler of the second undefinedundefined.then. That handler is in the line undefinedundefined(**), it shows undefinedundefined2 and does the same thing.undefinedundefined

    undefinedundefined

    So the output is the same as in the previous example: 1 -> 2 -> 4, but now with 1 second delay between undefinedundefinedalert calls.undefinedundefined

    undefinedundefined

    Returning promises allows us to build chains of asynchronous actions.

    undefinedundefined

    Example: loadScript

    undefinedundefined

    Let's use this feature with the promisified undefinedundefinedloadScript, defined in the undefinedundefinedprevious chapter, to load scripts one by one, in sequence:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run loadScript("/article/promise-chaining/one.js") .then(function(script) { return loadScript("/article/promise-chaining/two.js"); }) .then(function(script) { return loadScript("/article/promise-chaining/three.js"); }) .then(function(script) { // use functions declared in scripts // to show that they indeed loaded one(); two(); three(); });undefinedundefined

    undefinedundefined

    This code can be made bit shorter with arrow functions:

    undefinedundefined

    undefinedundefinedjs run loadScript("/article/promise-chaining/one.js") .then(script => loadScript("/article/promise-chaining/two.js")) .then(script => loadScript("/article/promise-chaining/three.js")) .then(script => { // scripts are loaded, we can use functions declared there one(); two(); three(); });undefinedundefined

    undefinedundefined

    Here each undefinedundefinedloadScript call returns a promise, and the next undefinedundefined.then runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.undefinedundefined

    undefinedundefined

    We can add more asynchronous actions to the chain. Please note that the code is still "flat" — it grows down, not to the right. There are no signs of the "pyramid of doom".

    undefinedundefined

    Technically, we could add undefinedundefined.then directly to each undefinedundefinedloadScript, like this:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run loadScript("/article/promise-chaining/one.js").then(script1 => { loadScript("/article/promise-chaining/two.js").then(script2 => { loadScript("/article/promise-chaining/three.js").then(script3 => { // this function has access to variables script1, script2 and script3 one(); two(); three(); }); }); });undefinedundefined

    undefinedundefined

    This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks.

    undefinedundefined

    People who start to use promises sometimes don't know about chaining, so they write it this way. Generally, chaining is preferred.

    undefinedundefined

    Sometimes it's ok to write undefinedundefined.then directly, because the nested function has access to the outer scope. In the example above the most nested callback has access to all variables undefinedundefinedscript1, undefinedundefinedscript2, undefinedundefinedscript3. But that's an exception rather than a rule.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Thenables" To be precise, a handler may return not exactly a promise, but a so-called "thenable" object - an arbitrary object that has a method.then`. It will be treated the same way as a promise.undefinedundefined

    undefinedundefined

    The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have an extended set of methods, but also be compatible with native promises, because they implement undefinedundefined.then.undefinedundefined

    undefinedundefined

    Here's an example of a thenable object:

    undefinedundefined

    run class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // resolve with this.numundefinedundefined2 after the 1 second setTimeout(() => resolve(this.num 2), 1000); // (**) } }undefinedundefined

    undefinedundefined

    new Promise(resolve => resolve(1)) .then(result => { undefinedundefined! return new Thenable(result); // (undefinedundefined) /!* }) .then(alert); // shows 2 after 1000msundefinedundefined

    undefinedundefined
    undefinedundefined
    JavaScript checks the object returned by the `.then` handler in line `(*)`: if it has a callable method named `then`, then it calls that method providing native functions `resolve`, `reject` as arguments (similar to an executor) and waits until one of them is called. In the example above `resolve(2)` is called after 1 second `(**)`. Then the result is passed further down the chain.
    This feature allows us to integrate custom objects with promise chains without having to inherit from `Promise`.undefinedundefined
    undefinedundefined

    Bigger example: fetch

    undefinedundefined

    In frontend programming promises are often used for network requests. So let's see an extended example of that.

    undefinedundefined

    We'll use the undefinedundefinedfetch method to load the information about the user from the remote server. It has a lot of optional parameters covered in undefinedundefinedseparate chapters, but the basic syntax is quite simple:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet promise undefinedundefined=undefinedundefinedfetch(url)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    This makes a network request to the undefinedundefinedurl and returns a promise. The promise resolves with a undefinedundefinedresponse object when the remote server responds with headers, but undefinedundefinedbefore the full response is downloaded.undefinedundefined

    undefinedundefined

    To read the full response, we should call the method undefinedundefinedresponse.text(): it returns a promise that resolves when the full text is downloaded from the remote server, with that text as a result.undefinedundefined

    undefinedundefined

    The code below makes a request to undefinedundefineduser.json and loads its text from the server:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run fetch('/article/promise-chaining/user.json') // .then below runs when the remote server responds .then(function(response) { // response.text() returns a new promise that resolves with the full response text // when it loads return response.text(); }) .then(function(text) { // ...and here's the content of the remote file alert(text); // {"name": "iliakan", "isAdmin": true} });undefinedundefined

    undefinedundefined

    The undefinedundefinedresponse object returned from undefinedundefinedfetch also includes the method undefinedundefinedresponse.json() that reads the remote data and parses it as JSON. In our case that's even more convenient, so let's switch to it.undefinedundefined

    undefinedundefined

    We'll also use arrow functions for brevity:

    undefinedundefined

    undefinedundefinedjs run // same as above, but response.json() parses the remote content as JSON fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => alert(user.name)); // iliakan, got user nameundefinedundefined

    undefinedundefined

    Now let's do something with the loaded user.

    undefinedundefined

    For instance, we can make one more request to GitHub, load the user profile and show the avatar:

    undefinedundefined

    ``undefinedundefinedjs run // Make a request for user.json fetch('/article/promise-chaining/user.json') // Load it as json .then(response => response.json()) // Make a request to GitHub .then(user => fetch(https://api.github.com/users/${user.name}`)) // Load the response as json .then(response => response.json()) // Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it) .then(githubUser => { let img = document.createElement(‘img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img);undefinedundefined

    undefinedundefined
    undefinedundefinedsetTimeout(() => img.remove(), 3000); // (*)undefinedundefined
    undefinedundefined

    });

    undefinedundefined

    The code works; see comments about the details. However, there's a potential problem in it, a typical error for those who begin to use promises.

    undefinedundefined

    Look at the line undefinedundefined(*): how can we do something undefinedundefinedafter the avatar has finished showing and gets removed? For instance, we'd like to show a form for editing that user or something else. As of now, there's no way.undefinedundefined

    undefinedundefined

    To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing.

    undefinedundefined

    Like this:

    undefinedundefined

    ``undefinedundefinedjs run fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(https://api.github.com/users/${user.name}`)) .then(response => response.json()) undefinedundefined! .then(githubUser => new Promise(function(resolve, reject) { // (undefinedundefined) /!* let img = document.createElement(‘img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img);undefinedundefined

    undefinedundefined
    undefinedundefinedsetTimeout(() => {
    img.remove();undefinedundefined
    undefinedundefined

    undefinedundefined! resolve(githubUser); // (**) undefinedundefined/! }, 3000); })) // triggers after 3 seconds .then(githubUser => alert(undefinedundefinedFinished showing ${githubUser.name})); undefinedundefined

    undefinedundefined

    That is, the undefinedundefined.then handler in line undefinedundefined(*) now returns undefinedundefinednew Promise, that becomes settled only after the call of undefinedundefinedresolve(githubUser) in undefinedundefinedsetTimeoutundefinedundefined(**). The next undefinedundefined.then in the chain will wait for that.undefinedundefined

    undefinedundefined

    As a good practice, an asynchronous action should always return a promise. That makes it possible to plan actions after it; even if we don't plan to extend the chain now, we may need it later.

    undefinedundefined

    Finally, we can split the code into reusable functions:

    undefinedundefined

    run function loadJson(url) { return fetch(url) .then(response => response.json()); }

    undefinedundefined

    function loadGithubUser(name) { return fetch(undefinedundefinedhttps://api.github.com/users/${name}) .then(response => response.json()); }undefinedundefined

    undefinedundefined

    function showAvatar(githubUser) { return new Promise(function(resolve, reject) { let img = document.createElement(‘img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img);

    undefinedundefined
    undefinedundefinedsetTimeout(() => {
    img.remove();
    resolve(githubUser);
    }, 3000);undefinedundefined
    undefinedundefined

    }); }

    undefinedundefined

    // Use them: loadJson(‘/article/promise-chaining/user.json') .then(user => loadGithubUser(user.name)) .then(showAvatar) .then(githubUser => alert(undefinedundefinedFinished showing ${githubUser.name})); // … undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    If a undefinedundefined.then (or undefinedundefinedcatch/finally, doesn't matter) handler returns a promise, the rest of the chain waits until it settles. When it does, its result (or error) is passed further.undefinedundefined

    undefinedundefined

    Here's a full picture:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Error handling with promises

    undefinedundefined

    Promise chains are great at error handling. When a promise rejects, the control jumps to the closest rejection handler. That's very convenient in practice.

    undefinedundefined

    For instance, in the code below the URL to undefinedundefinedfetch is wrong (no such site) and undefinedundefined.catch handles the error:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run *!* fetch('https://no-such-server.blabla') // rejects */!* .then(response => response.json()) .catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)undefinedundefined

    undefinedundefined

    As you can see, the undefinedundefined.catch doesn't have to be immediate. It may appear after one or maybe several undefinedundefined.then.undefinedundefined

    undefinedundefined

    Or, maybe, everything is all right with the site, but the response is not valid JSON. The easiest way to catch all errors is to append undefinedundefined.catch to the end of chain:undefinedundefined

    undefinedundefined

    ``undefinedundefinedjs run fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise((resolve, reject) => { let img = document.createElement(‘img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img);undefinedundefined

    undefinedundefined
    undefinedundefinedsetTimeout(() => {
    img.remove();
    resolve(githubUser);
    }, 3000);undefinedundefined
    undefinedundefined

    })) undefinedundefined! .catch(error => alert(error.message)); undefinedundefined/! undefinedundefined

    undefinedundefined

    Normally, such undefinedundefined.catch doesn't trigger at all. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.undefinedundefined

    undefinedundefined

    Implicit try..catch

    undefinedundefined

    The code of a promise executor and promise handlers has an "invisible undefinedundefinedtry..catch" around it. If an exception happens, it gets caught and treated as a rejection.undefinedundefined

    undefinedundefined

    For instance, this code:

    undefinedundefined

    undefinedundefinedjs run new Promise((resolve, reject) => { *!* throw new Error("Whoops!"); */!* }).catch(alert); // Error: Whoops!undefinedundefined

    undefinedundefined

    …Works exactly the same as this:

    undefinedundefined

    undefinedundefinedjs run new Promise((resolve, reject) => { *!* reject(new Error("Whoops!")); */!* }).catch(alert); // Error: Whoops!undefinedundefined

    undefinedundefined

    The "invisible undefinedundefinedtry..catch" around the executor automatically catches the error and turns it into rejected promise.undefinedundefined

    undefinedundefined

    This happens not only in the executor function, but in its handlers as well. If we undefinedundefinedthrow inside a undefinedundefined.then handler, that means a rejected promise, so the control jumps to the nearest error handler.undefinedundefined

    undefinedundefined

    Here's an example:

    undefinedundefined

    undefinedundefinedjs run new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { *!* throw new Error("Whoops!"); // rejects the promise */!* }).catch(alert); // Error: Whoops!undefinedundefined

    undefinedundefined

    This happens for all errors, not just those caused by the undefinedundefinedthrow statement. For example, a programming error:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { *!* blabla(); // no such function */!* }).catch(alert); // ReferenceError: blabla is not definedundefinedundefined

    undefinedundefined

    The final undefinedundefined.catch not only catches explicit rejections, but also accidental errors in the handlers above.undefinedundefined

    undefinedundefined

    Rethrowing

    undefinedundefined

    As we already noticed, undefinedundefined.catch at the end of the chain is similar to undefinedundefinedtry..catch. We may have as many undefinedundefined.then handlers as we want, and then use a single undefinedundefined.catch at the end to handle errors in all of them.undefinedundefined

    undefinedundefined

    In a regular undefinedundefinedtry..catch we can analyze the error and maybe rethrow it if it can't be handled. The same thing is possible for promises.undefinedundefined

    undefinedundefined

    If we undefinedundefinedthrow inside undefinedundefined.catch, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the next closest successful undefinedundefined.then handler.undefinedundefined

    undefinedundefined

    In the example below the undefinedundefined.catch successfully handles the error:undefinedundefined

    undefinedundefined

    run // the execution: catch -> then new Promise((resolve, reject) => {

    undefinedundefined

    throw new Error("Whoops!");

    undefinedundefined

    }).catch(function(error) {

    undefinedundefined

    alert("The error is handled, continue normally");

    undefinedundefined

    }).then(() => alert("Next successful handler runs"));

    undefinedundefined

    Here the undefinedundefined.catch block finishes normally. So the next successful undefinedundefined.then handler is called.undefinedundefined

    undefinedundefined

    In the example below we see the other situation with undefinedundefined.catch. The handler undefinedundefined(*) catches the error and just can't handle it (e.g. it only knows how to handle undefinedundefinedURIError), so it throws it again:undefinedundefined

    undefinedundefined

    run // the execution: catch -> catch new Promise((resolve, reject) => {

    undefinedundefined

    throw new Error("Whoops!");

    undefinedundefined

    }).catch(function(error) { // (*)

    undefinedundefined

    if (error instanceof URIError) { // handle it } else { alert("Can't handle such error");

    undefinedundefined

    undefinedundefined! throw error; // throwing this or another error jumps to the next catch undefinedundefined/! }undefinedundefined

    undefinedundefined

    }).then(function() { /* doesn't run here */ }).catch(error => { // (**)

    undefinedundefined

    alert(undefinedundefinedThe unknown error has occurred: ${error}); // don't return anything => execution goes the normal wayundefinedundefined

    undefinedundefined

    });

    undefinedundefined

    The execution jumps from the first undefinedundefined.catchundefinedundefined(*) to the next one undefinedundefined(**) down the chain.undefinedundefined

    undefinedundefined

    Unhandled rejections

    undefinedundefined

    What happens when an error is not handled? For instance, we forgot to append undefinedundefined.catch to the end of the chain, like here:undefinedundefined

    undefinedundefined

    undefinedundefinedjs untrusted run refresh new Promise(function() { noSuchFunction(); // Error here (no such function) }) .then(() => { // successful promise handlers, one or more }); // without .catch at the end!undefinedundefined

    undefinedundefined

    In case of an error, the promise becomes rejected, and the execution should jump to the closest rejection handler. But there is none. So the error gets "stuck". There's no code to handle it.

    undefinedundefined

    In practice, just like with regular unhandled errors in code, it means that something has gone terribly wrong.

    undefinedundefined

    What happens when a regular error occurs and is not caught by undefinedundefinedtry..catch? The script dies with a message in the console. A similar thing happens with unhandled promise rejections.undefinedundefined

    undefinedundefined

    The JavaScript engine tracks such rejections and generates a global error in that case. You can see it in the console if you run the example above.

    undefinedundefined

    In the browser we can catch such errors using the event undefinedundefinedunhandledrejection:undefinedundefined

    undefinedundefined

    run undefinedundefined! window.addEventListener(‘unhandledrejection', function(event) { // the event object has two special properties: alert(event.promise); // [object Promise] - the promise that generated the error alert(event.reason); // Error: Whoops! - the unhandled error object }); undefinedundefined/!undefinedundefined

    undefinedundefined

    new Promise(function() { throw new Error("Whoops!"); }); // no catch to handle the error

    undefinedundefined

    The event is the part of the undefinedundefinedHTML standard.undefinedundefined

    undefinedundefined

    If an error occurs, and there's no undefinedundefined.catch, the undefinedundefinedunhandledrejection handler triggers, and gets the undefinedundefinedevent object with the information about the error, so we can do something.undefinedundefined

    undefinedundefined

    Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server.

    undefinedundefined

    In non-browser environments like Node.js there are other ways to track unhandled errors.

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • undefinedundefined.catch handles errors in promises of all kinds: be it a undefinedundefinedreject() call, or an error thrown in a handler.undefinedundefined
    • undefinedundefined
    • We should place undefinedundefined.catch exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones (maybe they are programming mistakes).undefinedundefined
    • undefinedundefined
    • It's ok not to use undefinedundefined.catch at all, if there's no way to recover from an error.undefinedundefined
    • undefinedundefined
    • In any case we should have the undefinedundefinedunhandledrejection event handler (for browsers, and analogs for other environments) to track unhandled errors and inform the user (and probably our server) about them, so that our app never "just dies".undefinedundefined
    • undefinedundefined
    undefinedundefined

    Promise API

    undefinedundefined

    There are 6 static methods in the undefinedundefinedPromise class. We'll quickly cover their use cases here.undefinedundefined

    undefinedundefined

    Promise.all

    undefinedundefined

    Let's say we want many promises to execute in parallel and wait until all of them are ready.

    undefinedundefined

    For instance, download several URLs in parallel and process the content once they are all done.

    undefinedundefined

    That's what undefinedundefinedPromise.all is for.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    undefinedundefinedPromise.all takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.undefinedundefined

    undefinedundefined

    The new promise resolves when all listed promises are settled, and the array of their results becomes its result.

    undefinedundefined

    For instance, the undefinedundefinedPromise.all below settles after 3 seconds, and then its result is an array undefinedundefined[1, 2, 3]:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array memberundefinedundefined

    undefinedundefined

    Please note that the order of the resulting array members is the same as in its source promises. Even though the first promise takes the longest time to resolve, it's still first in the array of results.

    undefinedundefined

    A common trick is to map an array of job data into an array of promises, and then wrap that into undefinedundefinedPromise.all.undefinedundefined

    undefinedundefined

    For instance, if we have an array of URLs, we can fetch them all like this:

    undefinedundefined

    run let urls = [ ‘https://api.github.com/users/iliakan', ‘https://api.github.com/users/remy', ‘https://api.github.com/users/jeresig'];

    undefinedundefined

    // map every url to the promise of the fetch let requests = urls.map(url => fetch(url));

    undefinedundefined

    // Promise.all waits until all jobs are resolved Promise.all(requests) .then(responses => responses.forEach( response => alert(undefinedundefined${response.url}: ${response.status}) )); undefinedundefined

    undefinedundefined

    A bigger example with fetching user information for an array of GitHub users by their names (we could fetch an array of goods by their ids, the logic is identical):

    undefinedundefined

    run let names = [‘iliakan', ‘remy', ‘jeresig'];

    undefinedundefined

    let requests = names.map(name => fetch(undefinedundefinedhttps://api.github.com/users/${name}));undefinedundefined

    undefinedundefined

    Promise.all(requests) .then(responses => { // all responses are resolved successfully for(let response of responses) { alert(undefinedundefined${response.url}: ${response.status}); // shows 200 for every url }undefinedundefined

    undefinedundefined
    undefinedundefinedreturn responses;undefinedundefined
    undefinedundefined

    }) // map array of responses into an array of response.json() to read their content .then(responses => Promise.all(responses.map(r => r.json()))) // all JSON answers are parsed: "users" is the array of them .then(users => users.forEach(user => alert(user.name)));

    undefinedundefined

    undefinedundefinedIf any of the promises is rejected, the promise returned by undefinedundefinedPromise.all immediately rejects with that error.undefinedundefinedundefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), *!* new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), */!* new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).catch(alert); // Error: Whoops!undefinedundefined

    undefinedundefined

    Here the second promise rejects in two seconds. That leads to an immediate rejection of undefinedundefinedPromise.all, so undefinedundefined.catch executes: the rejection error becomes the outcome of the entire undefinedundefinedPromise.all.undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="In case of an error, other promises are ignored" If one promise rejects,Promise.all` immediately rejects, completely forgetting about the other ones in the list. Their results are ignored.undefinedundefined

    undefinedundefined

    For example, if there are multiple undefinedundefinedfetch calls, like in the example above, and one fails, the others will still continue to execute, but undefinedundefinedPromise.all won't watch them anymore. They will probably settle, but their results will be ignored.undefinedundefined

    undefinedundefined

    undefinedundefinedPromise.all does nothing to cancel them, as there's no concept of "cancellation" in promises. In undefinedundefinedanother chapter we'll cover undefinedundefinedAbortController that can help with that, but it's not a part of the Promise API. undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Promise.all(iterable)undefinedundefinedallows non-promise \"regular\" values initerableundefinedundefined" Normally,Promise.all(…)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's passed to the resulting array "as is".undefinedundefined

    undefinedundefined

    For instance, here the results are undefinedundefined[1, 2, 3]:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), 2, 3 ]).then(alert); // 1, 2, 3undefinedundefined

    undefinedundefined

    So we are able to pass ready values to undefinedundefinedPromise.all where convenient. undefinedundefined

    undefinedundefined

    Promise.allSettled

    undefinedundefined

    [recent browser="new"]

    undefinedundefined

    undefinedundefinedPromise.all rejects as a whole if any promise rejects. That's good for "all or nothing" cases, when we need undefinedundefinedall results successful to proceed:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefinedPromise.allSettled just waits for all promises to settle, regardless of the result. The resulting array has:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefined{status:"fulfilled", value:result} for successful responses,undefinedundefined
    • undefinedundefined
    • undefinedundefined{status:"rejected", reason:error} for errors.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For example, we'd like to fetch the information about multiple users. Even if one request fails, we're still interested in the others.

    undefinedundefined

    Let's use undefinedundefinedPromise.allSettled:undefinedundefined

    undefinedundefined

    run let urls = [ ‘https://api.github.com/users/iliakan', ‘https://api.github.com/users/remy', ‘https://no-such-url'];

    undefinedundefined

    Promise.allSettled(urls.map(url => fetch(url))) .then(results => { // (*) results.forEach((result, num) => { if (result.status == "fulfilled") { alert(undefinedundefined${urls[num]}: ${result.value.status}); } if (result.status == "rejected") { alert(undefinedundefined${urls[num]}: ${result.reason}); } }); }); undefinedundefined

    undefinedundefined

    The undefinedundefinedresults in the line undefinedundefined(*) above will be:undefinedundefined

    undefinedundefinedundefinedundefined

    So for each promise we get its status and undefinedundefinedvalue/error.undefinedundefined

    undefinedundefined

    Polyfill

    undefinedundefined

    If the browser doesn't support undefinedundefinedPromise.allSettled, it's easy to polyfill:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedif (undefinedundefined!undefinedundefinedPromise.undefinedundefinedallSettled) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedconst rejectHandler undefinedundefined= reason undefinedundefined=> (undefinedundefined{undefinedundefinedstatusundefinedundefined:undefinedundefined'rejected'undefinedundefined, reason undefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedconst resolveHandler undefinedundefined= value undefinedundefined=> (undefinedundefined{undefinedundefinedstatusundefinedundefined:undefinedundefined'fulfilled'undefinedundefined, value undefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedPromise.undefinedundefinedallSettledundefinedundefined=undefinedundefinedfunction (promises) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedconst convertedPromises undefinedundefined=undefinedundefinedpromises.undefinedundefinedmap(p undefinedundefined=>undefinedundefinedPromise.undefinedundefinedresolve(p).undefinedundefinedthen(resolveHandlerundefinedundefined, rejectHandler))undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedreturnundefinedundefinedPromise.undefinedundefinedall(convertedPromises)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    In this code, undefinedundefinedpromises.map takes input values, turns them into promises (just in case a non-promise was passed) with undefinedundefinedp => Promise.resolve(p), and then adds undefinedundefined.then handler to every one.undefinedundefined

    undefinedundefined

    That handler turns a successful result undefinedundefinedvalue into undefinedundefined{status:'fulfilled', value}, and an error undefinedundefinedreason into undefinedundefined{status:'rejected', reason}. That's exactly the format of undefinedundefinedPromise.allSettled.undefinedundefined

    undefinedundefined

    Now we can use undefinedundefinedPromise.allSettled to get the results of undefinedundefinedall given promises, even if some of them reject.undefinedundefined

    undefinedundefined

    Promise.race

    undefinedundefined

    Similar to undefinedundefinedPromise.all, but waits only for the first settled promise and gets its result (or error).undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet promise undefinedundefined=undefinedundefinedPromise.undefinedundefinedrace(iterable)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    For instance, here the result will be undefinedundefined1:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1undefinedundefined

    undefinedundefined

    The first promise here was fastest, so it became the result. After the first settled promise "wins the race", all further results/errors are ignored.

    undefinedundefined

    Promise.any

    undefinedundefined

    Similar to undefinedundefinedPromise.race, but waits only for the first fulfilled promise and gets its result. If all of the given promises are rejected, then the returned promise is rejected with undefinedundefinedundefinedundefinedAggregateErrorundefinedundefined - a special error object that stores all promise errors in its undefinedundefinederrors property.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet promise undefinedundefined=undefinedundefinedPromise.undefinedundefinedany(iterable)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    For instance, here the result will be undefinedundefined1:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)), new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1undefinedundefined

    undefinedundefined

    The first promise here was fastest, but it was rejected, so the second promise became the result. After the first fulfilled promise "wins the race", all further results are ignored.

    undefinedundefined

    Here's an example when all promises fail:

    undefinedundefined

    undefinedundefinedjs run Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000)) ]).catch(error => { console.log(error.constructor.name); // AggregateError console.log(error.errors[0]); // Error: Ouch! console.log(error.errors[1]); // Error: Error });undefinedundefined

    undefinedundefined

    As you can see, error objects for failed promises are available in the undefinedundefinederrors property of the undefinedundefinedAggregateError object.undefinedundefined

    undefinedundefined

    Promise.resolve/reject

    undefinedundefined

    Methods undefinedundefinedPromise.resolve and undefinedundefinedPromise.reject are rarely needed in modern code, because undefinedundefinedasync/await syntax (we'll cover it undefinedundefineda bit later) makes them somewhat obsolete.undefinedundefined

    undefinedundefined

    We cover them here for completeness and for those who can't use undefinedundefinedasync/await for some reason.undefinedundefined

    undefinedundefined

    Promise.resolve

    undefinedundefined

    undefinedundefinedPromise.resolve(value) creates a resolved promise with the result undefinedundefinedvalue.undefinedundefined

    undefinedundefined

    Same as:

    undefinedundefinedundefinedundefined

    The method is used for compatibility, when a function is expected to return a promise.

    undefinedundefined

    For example, the undefinedundefinedloadCached function below fetches a URL and remembers (caches) its content. For future calls with the same URL it immediately gets the previous content from cache, but uses undefinedundefinedPromise.resolve to make a promise of it, so the returned value is always a promise:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet cache undefinedundefined=undefinedundefinednewundefinedundefinedMap()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedloadCached(url) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedif (undefinedundefinedcache.undefinedundefinedhas(url)) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefinedreturnundefinedundefinedPromise.undefinedundefinedresolve(undefinedundefinedcache.undefinedundefinedget(url))undefinedundefined;undefinedundefined// (*)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined  }undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined  return fetchundefinedundefined(undefinedundefinedurlundefinedundefined)undefinedundefinedundefinedundefinedundefinedundefined    .thenundefinedundefined(undefinedundefinedresponse => response.textundefinedundefined())undefinedundefinedundefinedundefinedundefinedundefined    .thenundefinedundefined(undefinedundefinedtext => {undefinedundefinedundefinedundefinedundefinedundefined      cache.setundefinedundefined(undefinedundefinedurl,textundefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined      return text;undefinedundefinedundefinedundefinedundefinedundefined    }undefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    We can write undefinedundefinedloadCached(url).then(…), because the function is guaranteed to return a promise. We can always use undefinedundefined.then after undefinedundefinedloadCached. That's the purpose of undefinedundefinedPromise.resolve in the line undefinedundefined(*).undefinedundefined

    undefinedundefined

    Promise.reject

    undefinedundefined

    undefinedundefinedPromise.reject(error) creates a rejected promise with undefinedundefinederror.undefinedundefined

    undefinedundefined

    Same as:

    undefinedundefinedundefinedundefined

    In practice, this method is almost never used.

    undefinedundefined

    Summary

    undefinedundefined

    There are 6 static methods of undefinedundefinedPromise class:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedPromise.all(promises) - waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, it becomes the error of undefinedundefinedPromise.all, and all other results are ignored.undefinedundefined
    2. undefinedundefined
    3. undefinedundefinedPromise.allSettled(promises) (recently added method) - waits for all promises to settle and returns their results as an array of objects with: undefinedundefined
        undefinedundefined
      • undefinedundefinedstatus: undefinedundefined"fulfilled" or undefinedundefined"rejected"undefinedundefined
      • undefinedundefined
      • undefinedundefinedvalue (if fulfilled) or undefinedundefinedreason (if rejected).undefinedundefined
      • undefinedundefined
      undefinedundefined
    4. undefinedundefined
    5. undefinedundefinedPromise.race(promises) - waits for the first promise to settle, and its result/error becomes the outcome.undefinedundefined
    6. undefinedundefined
    7. undefinedundefinedPromise.any(promises) (recently added method) - waits for the first promise to fulfill, and its result becomes the outcome. If all of the given promises are rejected, undefinedundefinedundefinedundefinedAggregateErrorundefinedundefined becomes the error of undefinedundefinedPromise.any.undefinedundefined
    8. undefinedundefined
    9. undefinedundefinedPromise.resolve(value) - makes a resolved promise with the given value.undefinedundefined
    10. undefinedundefined
    11. undefinedundefinedPromise.reject(error) - makes a rejected promise with the given error.undefinedundefined
    12. undefinedundefined
    undefinedundefined

    Of all these, undefinedundefinedPromise.all is probably the most common in practice.undefinedundefined

    undefinedundefined

    Variables

    undefinedundefined

    Most of the time, a JavaScript application needs to work with information. Here are two examples: 1. An online shop - the information might include goods being sold and a shopping cart. 2. A chat application - the information might include users, messages, and much more.

    undefinedundefined

    Variables are used to store this information.

    undefinedundefined

    A variable

    undefinedundefined

    A undefinedundefinedvariable is a "named storage" for data. We can use variables to store goodies, visitors, and other data.undefinedundefined

    undefinedundefined

    To create a variable in JavaScript, use the undefinedundefinedlet keyword.undefinedundefined

    undefinedundefined

    The statement below creates (in other words: undefinedundefineddeclares) a variable with the name "message":undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet messageundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Now, we can put some data into it by using the assignment operator undefinedundefined=:undefinedundefined

    undefinedundefinedundefinedundefined

    The string is now saved into the memory area associated with the variable. We can access it using the variable name:

    undefinedundefined

    run let message; message = ‘Hello!''';

    undefinedundefined

    undefinedundefined! alert(message); // shows the variable content undefinedundefined/! undefinedundefined

    undefinedundefined

    To be concise, we can combine the variable declaration and assignment into a single line:

    undefinedundefined

    run let message = ‘Hello!'''; // define the variable and assign the value

    undefinedundefined

    alert(message); // Hello!

    undefinedundefined

    We can also declare multiple variables in one line:

    undefinedundefined

    undefinedundefinedjs no-beautify let user = 'John', age = 25, message = 'Hello';undefinedundefined

    undefinedundefined

    That might seem shorter, but we don't recommend it. For the sake of better readability, please use a single line per variable.

    undefinedundefined

    The multiline variant is a bit longer, but easier to read:

    undefinedundefinedundefinedundefined

    Some people also define multiple variables in this multiline style: undefinedundefinedjs no-beautify let user = 'John', age = 25, message = 'Hello';undefinedundefined

    undefinedundefined

    …Or even in the "comma-first" style:

    undefinedundefined

    undefinedundefinedjs no-beautify let user = 'John' , age = 25 , message = 'Hello';undefinedundefined

    undefinedundefined

    Technically, all these variants do the same thing. So, it's a matter of personal taste and aesthetics.

    undefinedundefined

    undefinedundefinedsmart header="varundefinedundefinedinstead ofletundefinedundefined" In older scripts, you may also find another keyword:varundefinedundefinedinstead oflet`:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedvarundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefined message = 'Hello';undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The undefinedundefinedvar keyword is undefinedundefinedalmost the same as undefinedundefinedlet. It also declares a variable, but in a slightly different, "old-school" way.undefinedundefined

    undefinedundefined

    There are subtle differences between undefinedundefinedlet and undefinedundefinedvar, but they do not matter for us yet. We'll cover them in detail in the chapter undefinedundefinedinfo:var. undefinedundefined

    undefinedundefined

    A real-life analogy

    undefinedundefined

    We can easily grasp the concept of a "variable" if we imagine it as a "box" for data, with a uniquely-named sticker on it.

    undefinedundefined

    For instance, the variable undefinedundefinedmessage can be imagined as a box labeled undefinedundefined"message" with the value undefinedundefined"Hello!" in it:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    We can put any value in the box.

    undefinedundefined

    We can also change it as many times as we want: run let message;

    undefinedundefined

    message = ‘Hello!''';

    undefinedundefined

    message = ‘World!'''; // value changed

    undefinedundefined

    alert(message);

    undefinedundefined

    When the value is changed, the old data is removed from the variable:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    We can also declare two variables and copy data from one into the other.

    undefinedundefined

    run let hello = ‘Hello world!''';

    undefinedundefined

    let message;

    undefinedundefined

    undefinedundefined! // copy ‘Hello world' from hello into message message = hello; undefinedundefined/!undefinedundefined

    undefinedundefined

    // now two variables hold the same data alert(hello); // Hello world! alert(message); // Hello world!

    undefinedundefined

    warn header="Declaring twice triggers an error" A variable should be declared only once.

    undefinedundefined

    A repeated declaration of the same variable is an error:

    undefinedundefined

    run let message = "This";

    undefinedundefined

    // repeated ‘let' leads to an error let message = "That"; // SyntaxError: ‘message' has already been declared

    undefinedundefined
    undefinedundefinedSo, we should declare a variable once and then refer to it without `let`.undefinedundefined
    undefinedundefined

    smart header="Functional languages" It's interesting to note that there exist undefinedundefinedfunctional programming languages, like undefinedundefinedScala or undefinedundefinedErlang that forbid changing variable values.undefinedundefined

    undefinedundefined

    In such languages, once the value is stored "in the box", it's there forever. If we need to store something else, the language forces us to create a new box (declare a new variable). We can't reuse the old one.

    undefinedundefined

    Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. Studying such a language (even if you're not planning to use it soon) is recommended to broaden the mind.

    undefinedundefined

    Variable naming [#variable-naming]

    undefinedundefined

    There are two limitations on variable names in JavaScript:

    undefinedundefined
      undefinedundefined
    1. The name must contain only letters, digits, or the symbols undefinedundefined$ and undefinedundefined_.undefinedundefined
    2. undefinedundefined
    3. The first character must not be a digit.
    4. undefinedundefined
    undefinedundefined

    Examples of valid names:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet userNameundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedlet test123undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    When the name contains multiple words, undefinedundefinedcamelCase is commonly used. That is: words go one after another, each word except first starting with a capital letter: undefinedundefinedmyVeryLongName.undefinedundefined

    undefinedundefined

    What's interesting - the dollar sign undefinedundefined'$' and the underscore undefinedundefined'_' can also be used in names. They are regular symbols, just like letters, without any special meaning.undefinedundefined

    undefinedundefined

    These names are valid:

    undefinedundefined

    run untrusted let $ = 1; // declared a variable with the name "$" let _ = 2; // and now a variable with the name "_"

    undefinedundefined

    alert($ + _); // 3

    undefinedundefined

    Examples of incorrect variable names:

    undefinedundefined

    no-beautify let 1a; // cannot start with a digit

    undefinedundefined

    let my-name; // hyphens ‘-''' aren't allowed in the name

    undefinedundefined

    undefinedundefinedsmart header="Case matters" Variables named `apple` and `AppLE` are two different variables.undefinedundefined

    undefinedundefined

    smart header="Non-Latin letters are allowed, but not recommended" It is possible to use any language, including cyrillic letters or even hieroglyphs, like this:

    undefinedundefinedundefinedundefined

    Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it some time.

    undefinedundefined

    warn header="Reserved names" There is a undefinedundefinedlist of reserved words, which cannot be used as variable names because they are used by the language itself.undefinedundefined

    undefinedundefined

    For example: undefinedundefinedlet, undefinedundefinedclass, undefinedundefinedreturn, and undefinedundefinedfunction are reserved.undefinedundefined

    undefinedundefined

    The code below gives a syntax error:

    undefinedundefined

    undefinedundefinedjs run no-beautify let let = 5; // can't name a variable "let", error! let return = 5; // also can't name it "return", error! undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="An assignment withoutuse strict`"undefinedundefined

    undefinedundefined

    Normally, we need to define a variable before using it. But in the old times, it was technically possible to create a variable by a mere assignment of the value without using undefinedundefinedlet. This still works now if we don't put undefinedundefineduse strict in our scripts to maintain compatibility with old scripts.undefinedundefined

    undefinedundefined

    run no-strict // note: no "use strict" in this example

    undefinedundefined

    num = 5; // the variable "num" is created if it didn't exist

    undefinedundefined

    alert(num); // 5

    undefinedundefined

    This is a bad practice and would cause an error in strict mode:

    undefinedundefinedundefinedundefined

    undefinedundefined

    Constants

    undefinedundefined

    To declare a constant (unchanging) variable, use undefinedundefinedconst instead of undefinedundefinedlet:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedconst myBirthday undefinedundefined=undefinedundefined'18.04.1982'undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Variables declared using undefinedundefinedconst are called "constants". They cannot be reassigned. An attempt to do so would cause an error:undefinedundefined

    undefinedundefined

    run const myBirthday = ‘18.04.1982';

    undefinedundefined

    myBirthday = ‘01.01.2001'; // error, can't reassign the constant!

    undefinedundefined

    When a programmer is sure that a variable will never change, they can declare it with undefinedundefinedconst to guarantee and clearly communicate that fact to everyone.undefinedundefined

    undefinedundefined

    Uppercase constants

    undefinedundefined

    There is a widespread practice to use constants as aliases for difficult-to-remember values that are known prior to execution.

    undefinedundefined

    Such constants are named using capital letters and underscores.

    undefinedundefined

    For instance, let's make constants for colors in so-called "web" (hexadecimal) format:

    undefinedundefined

    run const COLOR_RED = "#F00"; const COLOR_GREEN = "#0F0"; const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00";

    undefinedundefined

    // …when we need to pick a color let color = COLOR_ORANGE; alert(color); // #FF7F00

    undefinedundefined

    Benefits:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedCOLOR_ORANGE is much easier to remember than undefinedundefined"#FF7F00".undefinedundefined
    • undefinedundefined
    • It is much easier to mistype undefinedundefined"#FF7F00" than undefinedundefinedCOLOR_ORANGE.undefinedundefined
    • undefinedundefined
    • When reading the code, undefinedundefinedCOLOR_ORANGE is much more meaningful than undefinedundefined#FF7F00.undefinedundefined
    • undefinedundefined
    undefinedundefined

    When should we use capitals for a constant and when should we name it normally? Let's make that clear.

    undefinedundefined

    Being a "constant" just means that a variable's value never changes. But there are constants that are known prior to execution (like a hexadecimal value for red) and there are constants that are undefinedundefinedcalculated in run-time, during the execution, but do not change after their initial assignment.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedconst pageLoadTime undefinedundefined=undefinedundefined/* time taken by a webpage to load */undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The value of undefinedundefinedpageLoadTime is not known prior to the page load, so it's named normally. But it's still a constant because it doesn't change after assignment.undefinedundefined

    undefinedundefined

    In other words, capital-named constants are only used as aliases for "hard-coded" values.

    undefinedundefined

    Name things right

    undefinedundefined

    Talking about variables, there's one more extremely important thing.

    undefinedundefined

    A variable name should have a clean, obvious meaning, describing the data that it stores.

    undefinedundefined

    Variable naming is one of the most important and complex skills in programming. A quick glance at variable names can reveal which code was written by a beginner versus an experienced developer.

    undefinedundefined

    In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labeled. Or, in other words, when the variables have good names.

    undefinedundefined

    Please spend time thinking about the right name for a variable before declaring it. Doing so will repay you handsomely.

    undefinedundefined

    Some good-to-follow rules are:

    undefinedundefined
      undefinedundefined
    • Use human-readable names like undefinedundefineduserName or undefinedundefinedshoppingCart.undefinedundefined
    • undefinedundefined
    • Stay away from abbreviations or short names like undefinedundefineda, undefinedundefinedb, undefinedundefinedc, unless you really know what you're doing.undefinedundefined
    • undefinedundefined
    • Make names maximally descriptive and concise. Examples of bad names are undefinedundefineddata and undefinedundefinedvalue. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing.undefinedundefined
    • undefinedundefined
    • Agree on terms within your team and in your own mind. If a site visitor is called a "user" then we should name related variables undefinedundefinedcurrentUser or undefinedundefinednewUser instead of undefinedundefinedcurrentVisitor or undefinedundefinednewManInTown.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Sounds simple? Indeed it is, but creating descriptive and concise variable names in practice is not. Go for it.

    undefinedundefined

    smart header="Reuse or create?" And the last note. There are some lazy programmers who, instead of declaring new variables, tend to reuse existing ones.

    undefinedundefined

    As a result, their variables are like boxes into which people throw different things without changing their stickers. What's inside the box now? Who knows? We need to come closer and check.

    undefinedundefined

    Such programmers save a little bit on variable declaration but lose ten times more on debugging.

    undefinedundefined

    An extra variable is good, not evil.

    undefinedundefined

    Modern JavaScript minifiers and browsers optimize code well enough, so it won't create performance issues. Using different variables for different values can even help the engine optimize your code.

    undefinedundefined

    Summary

    undefinedundefined

    We can declare variables to store data by using the undefinedundefinedvar, undefinedundefinedlet, or undefinedundefinedconst keywords.undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedlet - is a modern variable declaration.undefinedundefined
    • undefinedundefined
    • undefinedundefinedvar - is an old-school variable declaration. Normally we don't use it at all, but we'll cover subtle differences from undefinedundefinedlet in the chapter undefinedundefinedinfo:var, just in case you need them.undefinedundefined
    • undefinedundefined
    • undefinedundefinedconst - is like undefinedundefinedlet, but the value of the variable can't be changed.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Variables should be named in a way that allows us to easily understand what's inside them.

    undefinedundefined

    Promisification

    undefinedundefined

    "Promisification" is a long word for a simple transformation. It's the conversion of a function that accepts a callback into a function that returns a promise.

    undefinedundefined

    Such transformations are often required in real-life, as many functions and libraries are callback-based. But promises are more convenient, so it makes sense to promisify them.

    undefinedundefined

    For better understanding, let's see an example.

    undefinedundefined

    For instance, we have undefinedundefinedloadScript(src, callback) from the chapter undefinedundefinedinfo:callbacks.undefinedundefined

    undefinedundefined

    run function loadScript(src, callback) { let script = document.createElement(‘script'); script.src = src;

    undefinedundefined

    script.onload = () => callback(null, script); script.onerror = () => callback(new Error(undefinedundefinedScript load error for ${src}));undefinedundefined

    undefinedundefined

    document.head.append(script); }

    undefinedundefined

    // usage: // loadScript(‘path/script.js', (err, script) => {…})

    undefinedundefined

    The function loads a script with the given undefinedundefinedsrc, and then calls undefinedundefinedcallback(err) in case of an error, or undefinedundefinedcallback(null, script) in case of successful loading. That's a widespread agreement for using callbacks, we saw it before.undefinedundefined

    undefinedundefined

    Let's promisify it.

    undefinedundefined

    We'll make a new function undefinedundefinedloadScriptPromise(src), that does the same (loads the script), but returns a promise instead of using callbacks.undefinedundefined

    undefinedundefined

    In other words, we pass it only undefinedundefinedsrc (no undefinedundefinedcallback) and get a promise in return, that resolves with undefinedundefinedscript when the load is successful, and rejects with the error otherwise.undefinedundefined

    undefinedundefined

    Here it is:

    undefinedundefinedundefinedundefined

    As we can see, the new function is a wrapper around the original undefinedundefinedloadScript function. It calls it providing its own callback that translates to promise undefinedundefinedresolve/reject.undefinedundefined

    undefinedundefined

    Now undefinedundefinedloadScriptPromise fits well in promise-based code. If we like promises more than callbacks (and soon we'll see more reasons for that), then we will use it instead.undefinedundefined

    undefinedundefined

    In practice we may need to promisify more than one function, so it makes sense to use a helper.

    undefinedundefined

    We'll call it undefinedundefinedpromisify(f): it accepts a to-promisify function undefinedundefinedf and returns a wrapper function.undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedpromisify(f) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedreturnundefinedundefinedfunction (...undefinedundefinedargs) undefinedundefined{undefinedundefined// return a wrapper-function (*)undefinedundefinedundefinedundefinedundefinedundefinedreturnundefinedundefinednewundefinedundefinedPromise((resolveundefinedundefined, reject) undefinedundefined=>undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedcallback(errundefinedundefined, result) undefinedundefined{undefinedundefined// our custom callback for f (**)undefinedundefinedundefinedundefinedundefinedundefinedif (err) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedreject(err)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedelseundefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedresolve(result)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedargs.undefinedundefinedpush(callback)undefinedundefined;undefinedundefined// append our custom callback to the end of f argumentsundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedf.undefinedundefinedcall(undefinedundefinedthisundefinedundefined, ...undefinedundefinedargs)undefinedundefined;undefinedundefined// call the original functionundefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// usage:undefinedundefinedundefinedundefinedundefinedundefinedlet loadScriptPromise undefinedundefined=undefinedundefinedpromisify(loadScript)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedloadScriptPromise(...).undefinedundefinedthen(...)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The code may look a bit complex, but it's essentially the same that we wrote above, while promisifying undefinedundefinedloadScript function.undefinedundefined

    undefinedundefined

    A call to undefinedundefinedpromisify(f) returns a wrapper around undefinedundefinedfundefinedundefined(*). That wrapper returns a promise and forwards the call to the original undefinedundefinedf, tracking the result in the custom callback undefinedundefined(**).undefinedundefined

    undefinedundefined

    Here, undefinedundefinedpromisify assumes that the original function expects a callback with exactly two arguments undefinedundefined(err, result). That's what we encounter most often. Then our custom callback is in exactly the right format, and undefinedundefinedpromisify works great for such a case.undefinedundefined

    undefinedundefined

    But what if the original undefinedundefinedf expects a callback with more arguments undefinedundefinedcallback(err, res1, res2, ...)?undefinedundefined

    undefinedundefined

    We can improve our helper. Let's make a more advanced version of undefinedundefinedpromisify.undefinedundefined

    undefinedundefined
      undefinedundefined
    • When called as undefinedundefinedpromisify(f) it should work similar to the version above.undefinedundefined
    • undefinedundefined
    • When called as undefinedundefinedpromisify(f, true), it should return the promise that resolves with the array of callback results. That's exactly for callbacks with many arguments.undefinedundefined
    • undefinedundefined
    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// promisify(f, true) to get array of resultsundefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefinedpromisify(fundefinedundefined, manyArgs undefinedundefined=undefinedundefinedfalse) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedreturnundefinedundefinedfunction (...undefinedundefinedargs) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedreturnundefinedundefinednewundefinedundefinedPromise((resolveundefinedundefined, reject) undefinedundefined=>undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedfunctionundefinedundefined*!*undefinedundefinedcallback(errundefinedundefined, ...undefinedundefinedresultsundefinedundefined*undefinedundefined/!undefinedundefined*)undefinedundefined { // our custom callback for fundefinedundefinedundefinedundefinedundefinedundefined        if undefinedundefined(undefinedundefinederrundefinedundefined)undefinedundefined {undefinedundefinedundefinedundefinedundefinedundefined          rejectundefinedundefined(undefinedundefinederrundefinedundefined)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined        } else {undefinedundefinedundefinedundefinedundefinedundefined          // resolve with all callback results if manyArgs is specifiedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedresolveundefinedundefined(undefinedundefinedmanyArgs undefinedundefined?undefinedundefined results : resultsundefinedundefined[0])undefinedundefined;undefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedargs.undefinedundefinedpush(callback)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedf.undefinedundefinedcall(undefinedundefinedthisundefinedundefined, ...undefinedundefinedargs)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefined  }undefinedundefined;undefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined// usage:undefinedundefinedundefinedundefinedf undefinedundefined=undefinedundefinedpromisify(fundefinedundefined,undefinedundefinedtrue)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedf(...).undefinedundefinedthen(arrayOfResults undefinedundefined=> ...undefinedundefined, err undefinedundefined=> ...)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    As you can see it's essentially the same as above, but undefinedundefinedresolve is called with only one or all arguments depending on whether undefinedundefinedmanyArgs is truthy.undefinedundefined

    undefinedundefined

    For more exotic callback formats, like those without undefinedundefinederr at all: undefinedundefinedcallback(result), we can promisify such functions manually without using the helper.undefinedundefined

    undefinedundefined

    There are also modules with a bit more flexible promisification functions, e.g. undefinedundefinedes6-promisify. In Node.js, there's a built-in undefinedundefinedutil.promisify function for that.undefinedundefined

    undefinedundefined
    undefinedundefinedPromisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks.
    Remember, a promise may have only one result, but a callback may technically be called many times.
    So promisification is only meant for functions that call the callback once. Further calls will be ignored.undefinedundefined
    undefinedundefined

    Microtasks

    undefinedundefined

    Promise handlers undefinedundefined.then/undefinedundefined.catch/undefinedundefined.finally are always asynchronous.undefinedundefined

    undefinedundefined

    Even when a Promise is immediately resolved, the code on the lines undefinedundefinedbelowundefinedundefined.then/undefinedundefined.catch/undefinedundefined.finally will still execute before these handlers.undefinedundefined

    undefinedundefined

    Here's a demo:

    undefinedundefined

    run let promise = Promise.resolve();

    undefinedundefined

    promise.then(() => alert("promise done!"));

    undefinedundefined

    alert("code finished"); // this alert shows first

    undefinedundefined

    If you run it, you see undefinedundefinedcode finished first, and then undefinedundefinedpromise done!.undefinedundefined

    undefinedundefined

    That's strange, because the promise is definitely done from the beginning.

    undefinedundefined

    Why did the undefinedundefined.then trigger afterwards? What's going on?undefinedundefined

    undefinedundefined

    Microtasks queue

    undefinedundefined

    Asynchronous tasks need proper management. For that, the ECMA standard specifies an internal queue undefinedundefinedPromiseJobs, more often referred to as the "microtask queue" (V8 term).undefinedundefined

    undefinedundefined

    As stated in the undefinedundefinedspecification:undefinedundefined

    undefinedundefined
      undefinedundefined
    • The queue is first-in-first-out: tasks enqueued first are run first.
    • undefinedundefined
    • Execution of a task is initiated only when nothing else is running.
    • undefinedundefined
    undefinedundefined

    Or, to put it more simply, when a promise is ready, its undefinedundefined.then/catch/finally handlers are put into the queue; they are not executed yet. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it.undefinedundefined

    undefinedundefined

    That's why "code finished" in the example above shows first.

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Promise handlers always go through this internal queue.

    undefinedundefined

    If there's a chain with multiple undefinedundefined.then/catch/finally, then every one of them is executed asynchronously. That is, it first gets queued, then executed when the current code is complete and previously queued handlers are finished.undefinedundefined

    undefinedundefined

    undefinedundefinedWhat if the order matters for us? How can we make undefinedundefinedcode finished appear after undefinedundefinedpromise done?undefinedundefinedundefinedundefined

    undefinedundefined

    Easy, just put it into the queue with undefinedundefined.then:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run Promise.resolve() .then(() => alert("promise done!")) .then(() => alert("code finished"));undefinedundefined

    undefinedundefined

    Now the order is as intended.

    undefinedundefined

    Unhandled rejection

    undefinedundefined

    Remember the undefinedundefinedunhandledrejection event from the article undefinedundefinedinfo:promise-error-handling?undefinedundefined

    undefinedundefined

    Now we can see exactly how JavaScript finds out that there was an unhandled rejection.

    undefinedundefined

    undefinedundefinedAn "unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue.undefinedundefined

    undefinedundefined

    Normally, if we expect an error, we add undefinedundefined.catch to the promise chain to handle it:undefinedundefined

    undefinedundefined

    run let promise = Promise.reject(new Error("Promise Failed!")); undefinedundefined! promise.catch(err => alert(‘caught')); undefinedundefined/!undefinedundefined

    undefinedundefined

    // doesn't run: error handled window.addEventListener(‘unhandledrejection', event => alert(event.reason));

    undefinedundefined

    But if we forget to add undefinedundefined.catch, then, after the microtask queue is empty, the engine triggers the event:undefinedundefined

    undefinedundefined

    run let promise = Promise.reject(new Error("Promise Failed!"));

    undefinedundefined

    // Promise Failed! window.addEventListener(‘unhandledrejection', event => alert(event.reason));

    undefinedundefined

    What if we handle the error later? Like this:

    undefinedundefined

    run let promise = Promise.reject(new Error("Promise Failed!")); undefinedundefined! setTimeout(() => promise.catch(err => alert(‘caught')), 1000); undefinedundefined/!undefinedundefined

    undefinedundefined

    // Error: Promise Failed! window.addEventListener(‘unhandledrejection', event => alert(event.reason));

    undefinedundefined

    Now, if we run it, we'll see undefinedundefinedPromise Failed! first and then undefinedundefinedcaught.undefinedundefined

    undefinedundefined

    If we didn't know about the microtasks queue, we could wonder: "Why did undefinedundefinedunhandledrejection handler run? We did catch and handle the error!"undefinedundefined

    undefinedundefined

    But now we understand that undefinedundefinedunhandledrejection is generated when the microtask queue is complete: the engine examines promises and, if any of them is in the "rejected" state, then the event triggers.undefinedundefined

    undefinedundefined

    In the example above, undefinedundefined.catch added by undefinedundefinedsetTimeout also triggers. But it does so later, after undefinedundefinedunhandledrejection has already occurred, so it doesn't change anything.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (V8 term).

    undefinedundefined

    So undefinedundefined.then/catch/finally handlers are always called after the current code is finished.undefinedundefined

    undefinedundefined

    If we need to guarantee that a piece of code is executed after undefinedundefined.then/catch/finally, we can add it into a chained undefinedundefined.then call.undefinedundefined

    undefinedundefined

    In most Javascript engines, including browsers and Node.js, the concept of microtasks is closely tied with the "event loop" and "macrotasks". As these have no direct relation to promises, they are covered in another part of the tutorial, in the article undefinedundefinedinfo:event-loop.undefinedundefined

    undefinedundefined

    Async/await

    undefinedundefined

    There's a special syntax to work with promises in a more comfortable fashion, called "async/await". It's surprisingly easy to understand and use.

    undefinedundefined

    Async functions

    undefinedundefined

    Let's start with the undefinedundefinedasync keyword. It can be placed before a function, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    The word "async" before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.

    undefinedundefined

    For instance, this function returns a resolved promise with the result of undefinedundefined1; let's test it:undefinedundefined

    undefinedundefined

    run async function f() { return 1; }

    undefinedundefined

    f().then(alert); // 1

    undefinedundefined

    …We could explicitly return a promise, which would be the same:

    undefinedundefined

    run async function f() { return Promise.resolve(1); }

    undefinedundefined

    f().then(alert); // 1

    undefinedundefined

    So, undefinedundefinedasync ensures that the function returns a promise, and wraps non-promises in it. Simple enough, right? But not only that. There's another keyword, undefinedundefinedawait, that works only inside undefinedundefinedasync functions, and it's pretty cool.undefinedundefined

    undefinedundefined

    Await

    undefinedundefined

    The syntax:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// works only inside async functionsundefinedundefinedundefinedundefinedundefinedundefinedlet value undefinedundefined=undefinedundefinedawait promiseundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The keyword undefinedundefinedawait makes JavaScript wait until that promise settles and returns its result.undefinedundefined

    undefinedundefined

    Here's an example with a promise that resolves in 1 second: run async function f() {

    undefinedundefined

    let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) });

    undefinedundefined

    undefinedundefined! let result = await promise; // wait until the promise resolves (undefinedundefined) /!*undefinedundefined

    undefinedundefined

    alert(result); // "done!" }

    undefinedundefined

    f();

    undefinedundefined

    The function execution "pauses" at the line undefinedundefined(*) and resumes when the promise settles, with undefinedundefinedresult becoming its result. So the code above shows "done!" in one second.undefinedundefined

    undefinedundefined

    Let's emphasize: undefinedundefinedawait literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn't cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc.undefinedundefined

    undefinedundefined

    It's just a more elegant syntax of getting the promise result than undefinedundefinedpromise.then. And, it's easier to read and write.undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Can't useawaitundefinedundefinedin regular functions" If we try to useawait` in a non-async function, there would be a syntax error:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run function f() { let promise = Promise.resolve(1); *!* let result = await promise; // Syntax error */!* }undefinedundefined

    undefinedundefined

    We may get this error if we forget to put undefinedundefinedasync before a function. As stated earlier, undefinedundefinedawait only works inside an undefinedundefinedasync function. undefinedundefined

    undefinedundefined

    Let's take the undefinedundefinedshowAvatar() example from the chapter undefinedundefinedinfo:promise-chaining and rewrite it using undefinedundefinedasync/await:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. We'll need to replace undefinedundefined.then calls with undefinedundefinedawait.undefinedundefined
    2. undefinedundefined
    3. Also we should make the function undefinedundefinedasync for them to work.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    run async function showAvatar() {

    undefinedundefined

    // read our JSON let response = await fetch(‘/article/promise-chaining/user.json'); let user = await response.json();

    undefinedundefined

    // read github user let githubResponse = await fetch(undefinedundefinedhttps://api.github.com/users/${user.name}); let githubUser = await githubResponse.json();undefinedundefined

    undefinedundefined

    // show the avatar let img = document.createElement(‘img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img);

    undefinedundefined

    // wait 3 seconds await new Promise((resolve, reject) => setTimeout(resolve, 3000));

    undefinedundefined

    img.remove();

    undefinedundefined

    return githubUser; }

    undefinedundefined

    showAvatar();

    undefinedundefined

    Pretty clean and easy to read, right? Much better than before.

    undefinedundefined

    undefinedundefinedsmart header="awaitundefinedundefinedwon't work in the top-level code" People who are just starting to useawaitundefinedundefinedtend to forget the fact that we can't useawait` in top-level code. For example, this will not work:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run // syntax error in top-level code let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json();undefinedundefined

    undefinedundefined

    But we can wrap it into an anonymous async function, like this:

    undefinedundefinedundefinedundefined

    P.S. New feature: starting from V8 engine version 8.9+, top-level await works in undefinedundefinedmodules. undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="awaitundefinedundefinedaccepts \"thenables\"" Likepromise.thenundefinedundefined,awaitundefinedundefinedallows us to use thenable objects (those with a callablethenundefinedundefinedmethod). The idea is that a third-party object may not be a promise, but promise-compatible: if it supports.thenundefinedundefined, that's enough to use it withawait`.undefinedundefined

    undefinedundefined

    Here's a demo undefinedundefinedThenable class; the undefinedundefinedawait below accepts its instances:undefinedundefined

    undefinedundefined

    run class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // resolve with this.numundefinedundefined2 after 1000ms setTimeout(() => resolve(this.num 2), 1000); // (*) } }undefinedundefined

    undefinedundefined

    async function f() { // waits for 1 second, then result becomes 2 let result = await new Thenable(1); alert(result); }

    undefinedundefined

    f();

    undefinedundefined
    undefinedundefined
    If `await` gets a non-promise object with `.then`, it calls that method providing the built-in functions `resolve` and `reject` as arguments (just as it does for a regular `Promise` executor). Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.undefinedundefined
    undefinedundefined

    undefinedundefinedsmart header="Async class methods" To declare an async class method, just prepend it withasync`:undefinedundefined

    undefinedundefined

    run class Waiter { undefinedundefined! async wait() { undefinedundefined/! return await Promise.resolve(1); } }undefinedundefined

    undefinedundefined

    new Waiter() .wait() .then(alert); // 1 (this is the same as (result => alert(result)))

    undefinedundefined
    undefinedundefinedThe meaning is the same: it ensures that the returned value is a promise and enables `await`.
    undefinedundefined
    undefinedundefined

    Error handling

    undefinedundefined

    If a promise resolves normally, then undefinedundefinedawait promise returns the result. But in the case of a rejection, it throws the error, just as if there were a undefinedundefinedthrow statement at that line.undefinedundefined

    undefinedundefined

    This code:

    undefinedundefinedundefinedundefined

    …is the same as this:

    undefinedundefinedundefinedundefined

    In real situations, the promise may take some time before it rejects. In that case there will be a delay before undefinedundefinedawait throws an error.undefinedundefined

    undefinedundefined

    We can catch that error using undefinedundefinedtry..catch, the same way as a regular undefinedundefinedthrow:undefinedundefined

    undefinedundefined

    run async function f() {

    undefinedundefined

    try { let response = await fetch(‘http://no-such-url'); } catch(err) { undefinedundefined! alert(err); // TypeError: failed to fetch undefinedundefined/! } }undefinedundefined

    undefinedundefined

    f();

    undefinedundefined

    In the case of an error, the control jumps to the undefinedundefinedcatch block. We can also wrap multiple lines:undefinedundefined

    undefinedundefined

    run async function f() {

    undefinedundefined

    try { let response = await fetch(‘/no-user-here'); let user = await response.json(); } catch(err) { // catches errors both in fetch and response.json alert(err); } }

    undefinedundefined

    f();

    undefinedundefined

    If we don't have undefinedundefinedtry..catch, then the promise generated by the call of the async function undefinedundefinedf() becomes rejected. We can append undefinedundefined.catch to handle it:undefinedundefined

    undefinedundefined

    run async function f() { let response = await fetch(‘http://no-such-url'); }

    undefinedundefined

    // f() becomes a rejected promise undefinedundefined! f().catch(alert); // TypeError: failed to fetch // (undefinedundefined) /!* undefinedundefined

    undefinedundefined

    If we forget to add undefinedundefined.catch there, then we get an unhandled promise error (viewable in the console). We can catch such errors using a global undefinedundefinedunhandledrejection event handler as described in the chapter undefinedundefinedinfo:promise-error-handling.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="async/awaitundefinedundefinedandpromise.then/catchundefinedundefined" When we useasync/awaitundefinedundefined, we rarely need.thenundefinedundefined, becauseawaitundefinedundefinedhandles the waiting for us. And we can use a regulartry..catchundefinedundefinedinstead of.catch`. That's usually (but not always) more convenient.undefinedundefined

    undefinedundefined

    But at the top level of the code, when we're outside any undefinedundefinedasync function, we're syntactically unable to use undefinedundefinedawait, so it's a normal practice to add undefinedundefined.then/catch to handle the final result or falling-through error, like in the line undefinedundefined(*) of the example above. undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="async/awaitundefinedundefinedworks well withPromise.allundefinedundefined" When we need to wait for multiple promises, we can wrap them inPromise.allundefinedundefinedand thenawait`:undefinedundefined

    undefinedundefinedundefinedundefined

    In the case of an error, it propagates as usual, from the failed promise to undefinedundefinedPromise.all, and then becomes an exception that we can catch using undefinedundefinedtry..catch around the call.undefinedundefined

    undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    The undefinedundefinedasync keyword before a function has two effects:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Makes it always return a promise.
    2. undefinedundefined
    3. Allows undefinedundefinedawait to be used in it.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    The undefinedundefinedawait keyword before a promise makes JavaScript wait until that promise settles, and then:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. If it's an error, the exception is generated — same as if undefinedundefinedthrow error were called at that very place.undefinedundefined
    2. undefinedundefined
    3. Otherwise, it returns the result.
    4. undefinedundefined
    undefinedundefined

    Together they provide a great framework to write asynchronous code that is easy to both read and write.

    undefinedundefined

    With undefinedundefinedasync/await we rarely need to write undefinedundefinedpromise.then/catch, but we still shouldn't forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also undefinedundefinedPromise.all is nice when we are waiting for many tasks simultaneously.undefinedundefined

    undefinedundefined

    Generators

    undefinedundefined

    Regular functions return only one, single value (or nothing).

    undefinedundefined

    Generators can return ("yield") multiple values, one after another, on-demand. They work great with undefinedundefinediterables, allowing to create data streams with ease.undefinedundefined

    undefinedundefined

    Generator functions

    undefinedundefined

    To create a generator, we need a special syntax construct: undefinedundefinedfunction*, so-called "generator function".undefinedundefined

    undefinedundefined

    It looks like this:

    undefinedundefinedundefinedundefined

    Generator functions behave differently from regular ones. When such function is called, it doesn't run its code. Instead it returns a special object, called "generator object", to manage the execution.

    undefinedundefined

    Here, take a look:

    undefinedundefined

    run function* generateSequence() { yield 1; yield 2; return 3; }

    undefinedundefined

    // "generator function" creates "generator object" let generator = generateSequence(); undefinedundefined! alert(generator); // [object Generator] undefinedundefined/! undefinedundefined

    undefinedundefined

    The function code execution hasn't started yet:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The main method of a generator is undefinedundefinednext(). When called, it runs the execution until the nearest undefinedundefinedyield <value> statement (undefinedundefinedvalue can be omitted, then it's undefinedundefinedundefined). Then the function execution pauses, and the yielded undefinedundefinedvalue is returned to the outer code.undefinedundefined

    undefinedundefined

    The result of undefinedundefinednext() is always an object with two properties: - undefinedundefinedvalue: the yielded value. - undefinedundefineddone: undefinedundefinedtrue if the function code has finished, otherwise undefinedundefinedfalse.undefinedundefined

    undefinedundefined

    For instance, here we create the generator and get its first yielded value:

    undefinedundefined

    run function* generateSequence() { yield 1; yield 2; return 3; }

    undefinedundefined

    let generator = generateSequence();

    undefinedundefined

    undefinedundefined! let one = generator.next(); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(JSON.stringify(one)); // {value: 1, done: false}

    undefinedundefined

    As of now, we got the first value only, and the function execution is on the second line:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Let's call undefinedundefinedgenerator.next() again. It resumes the code execution and returns the next undefinedundefinedyield:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    And, if we call it a third time, the execution reaches the undefinedundefinedreturn statement that finishes the function:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Now the generator is done. We should see it from undefinedundefineddone:true and process undefinedundefinedvalue:3 as the final result.undefinedundefined

    undefinedundefined

    New calls to undefinedundefinedgenerator.next() don't make sense any more. If we do them, they return the same object: undefinedundefined{done: true}.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="function* f(…)undefinedundefinedorfunction *f(…)`?" Both syntaxes are correct.undefinedundefined

    undefinedundefined

    But usually the first syntax is preferred, as the star undefinedundefined* denotes that it's a generator function, it describes the kind, not the name, so it should stick with the undefinedundefinedfunction keyword. undefinedundefined

    undefinedundefined

    Generators are iterable

    undefinedundefined

    As you probably already guessed looking at the undefinedundefinednext() method, generators are undefinedundefinediterable.undefinedundefined

    undefinedundefined

    We can loop over their values using undefinedundefinedfor..of:undefinedundefined

    undefinedundefined

    run function* generateSequence() { yield 1; yield 2; return 3; }

    undefinedundefined

    let generator = generateSequence();

    undefinedundefined

    for(let value of generator) { alert(value); // 1, then 2 }

    undefinedundefined

    Looks a lot nicer than calling undefinedundefined.next().value, right?undefinedundefined

    undefinedundefined

    …But please note: the example above shows undefinedundefined1, then undefinedundefined2, and that's all. It doesn't show undefinedundefined3!undefinedundefined

    undefinedundefined

    It's because undefinedundefinedfor..of iteration ignores the last undefinedundefinedvalue, when undefinedundefineddone: true. So, if we want all results to be shown by undefinedundefinedfor..of, we must return them with undefinedundefinedyield:undefinedundefined

    undefinedundefined

    run function* generateSequence() { yield 1; yield 2; undefinedundefined! yield 3; undefinedundefined/! }undefinedundefined

    undefinedundefined

    let generator = generateSequence();

    undefinedundefined

    for(let value of generator) { alert(value); // 1, then 2, then 3 }

    undefinedundefined

    As generators are iterable, we can call all related functionality, e.g. the spread syntax undefinedundefined...:undefinedundefined

    undefinedundefined

    run function* generateSequence() { yield 1; yield 2; yield 3; }

    undefinedundefined

    let sequence = [0, …generateSequence()];

    undefinedundefined

    alert(sequence); // 0, 1, 2, 3

    undefinedundefined

    In the code above, undefinedundefined...generateSequence() turns the iterable generator object into an array of items (read more about the spread syntax in the chapter undefinedundefined)undefinedundefined

    undefinedundefined

    Using generators for iterables

    undefinedundefined

    Some time ago, in the chapter undefinedundefined we created an iterable undefinedundefinedrange object that returns values undefinedundefinedfrom..to.undefinedundefined

    undefinedundefined

    Here, let's remember the code:

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    // for..of range calls this method once in the very beginning undefinedundefinedSymbol.iterator { // …it returns the iterator object: // onward, for..of works only with that object, asking it for next values return { current: this.from, last: this.to,undefinedundefined

    undefinedundefined
    undefinedundefined  // next() is called on each iteration by the for..of loop
    next() {
    // it should return the value as an object {done:.., value :...}
    if (this.current <= this.last) {
    return { done: false, value: this.current++ };
    } else {
    return { done: true };
    }
    }
    };undefinedundefined
    undefinedundefined

    } };

    undefinedundefined

    // iteration over range returns numbers from range.from to range.to alert([…range]); // 1,2,3,4,5

    undefinedundefined

    We can use a generator function for iteration by providing it as undefinedundefinedSymbol.iterator.undefinedundefined

    undefinedundefined

    Here's the same undefinedundefinedrange, but much more compact:undefinedundefined

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    undefinedundefinedundefinedundefinedSymbol.iterator { // a shorthand for [Symbol.iterator]: functionundefinedundefined() for(let value = this.from; value <= this.to; value++) { yield value; } } };undefinedundefined

    undefinedundefined

    alert( […range] ); // 1,2,3,4,5

    undefinedundefined

    That works, because undefinedundefinedrange[Symbol.iterator]() now returns a generator, and generator methods are exactly what undefinedundefinedfor..of expects: - it has a undefinedundefined.next() method - that returns values in the form undefinedundefined{value: ..., done: true/false}undefinedundefined

    undefinedundefined

    That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easily.

    undefinedundefined

    The variant with a generator is much more concise than the original iterable code of undefinedundefinedrange, and keeps the same functionality.undefinedundefined

    undefinedundefined

    smart header="Generators may generate values forever" In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers.

    undefinedundefined

    That surely would require a undefinedundefinedbreak (or undefinedundefinedreturn) in undefinedundefinedfor..of over such generator. Otherwise, the loop would repeat forever and hang. undefinedundefined

    undefinedundefined

    Generator composition

    undefinedundefined

    Generator composition is a special feature of generators that allows to transparently "embed" generators in each other.

    undefinedundefined

    For instance, we have a function that generates a sequence of numbers:

    undefinedundefinedundefinedundefined

    Now we'd like to reuse it to generate a more complex sequence: - first, digits undefinedundefined0..9 (with character codes 48..57), - followed by uppercase alphabet letters undefinedundefinedA..Z (character codes 65..90) - followed by lowercase alphabet letters undefinedundefineda..z (character codes 97..122)undefinedundefined

    undefinedundefined

    We can use this sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first.

    undefinedundefined

    In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end.

    undefinedundefined

    For generators, there's a special undefinedundefinedyield* syntax to "embed" (compose) one generator into another.undefinedundefined

    undefinedundefined

    The composed generator:

    undefinedundefined

    run function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; }

    undefinedundefined

    function* generatePasswordCodes() {

    undefinedundefined

    undefinedundefined! // 0..9 yield* generateSequence(48, 57);undefinedundefined

    undefinedundefined

    // A..Z yield* generateSequence(65, 90);

    undefinedundefined

    // a..z yield* generateSequence(97, 122); undefinedundefined/!undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    let str = '';

    undefinedundefined

    for(let code of generatePasswordCodes()) { str += String.fromCharCode(code); }

    undefinedundefined

    alert(str); // 0..9A..Za..z

    undefinedundefined

    The undefinedundefinedyield* directive undefinedundefineddelegates the execution to another generator. This term means that undefinedundefinedyield* gen iterates over the generator undefinedundefinedgen and transparently forwards its yields outside. As if the values were yielded by the outer generator.undefinedundefined

    undefinedundefined

    The result is the same as if we inlined the code from nested generators:

    undefinedundefined

    run function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; }

    undefinedundefined

    function* generateAlphaNum() {

    undefinedundefined

    undefinedundefined! // yield* generateSequence(48, 57); for (let i = 48; i <= 57; i++) yield i;undefinedundefined

    undefinedundefined

    // yield* generateSequence(65, 90); for (let i = 65; i <= 90; i++) yield i;

    undefinedundefined

    // yield* generateSequence(97, 122); for (let i = 97; i <= 122; i++) yield i; undefinedundefined/!undefinedundefined

    undefinedundefined

    }

    undefinedundefined

    let str = '';

    undefinedundefined

    for(let code of generateAlphaNum()) { str += String.fromCharCode(code); }

    undefinedundefined

    alert(str); // 0..9A..Za..z

    undefinedundefined

    A generator composition is a natural way to insert a flow of one generator into another. It doesn't use extra memory to store intermediate results.

    undefinedundefined

    "yield" is a two-way street

    undefinedundefined

    Until this moment, generators were similar to iterable objects, with a special syntax to generate values. But in fact they are much more powerful and flexible.

    undefinedundefined

    That's because undefinedundefinedyield is a two-way street: it not only returns the result to the outside, but also can pass the value inside the generator.undefinedundefined

    undefinedundefined

    To do so, we should call undefinedundefinedgenerator.next(arg), with an argument. That argument becomes the result of undefinedundefinedyield.undefinedundefined

    undefinedundefined

    Let's see an example:

    undefinedundefined

    run function* gen() { undefinedundefined! // Pass a question to the outer code and wait for an answer let result = yield "2 + 2 = ?"; // (undefinedundefined) /!*undefinedundefined

    undefinedundefined

    alert(result); }

    undefinedundefined

    let generator = gen();

    undefinedundefined

    let question = generator.next().value; // <- yield returns the value

    undefinedundefined

    generator.next(4); // -> pass the result into the generatorundefinedundefined
    undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined
      undefinedundefined
    1. The first call undefinedundefinedgenerator.next() should be always made without an argument (the argument is ignored if passed). It starts the execution and returns the result of the first undefinedundefinedyield "2+2=?". At this point the generator pauses the execution, while staying on the line undefinedundefined(*).undefinedundefined
    2. undefinedundefined
    3. Then, as shown at the picture above, the result of undefinedundefinedyield gets into the undefinedundefinedquestion variable in the calling code.undefinedundefined
    4. undefinedundefined
    5. On undefinedundefinedgenerator.next(4), the generator resumes, and undefinedundefined4 gets in as the result: undefinedundefinedlet result = 4.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Please note, the outer code does not have to immediately call undefinedundefinednext(4). It may take time. That's not a problem: the generator will wait.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    As we can see, unlike regular functions, a generator and the calling code can exchange results by passing values in undefinedundefinednext/yield.undefinedundefined

    undefinedundefined

    To make things more obvious, here's another example, with more calls:

    undefinedundefined

    run function* gen() { let ask1 = yield "2 + 2 = ?";

    undefinedundefined

    alert(ask1); // 4

    undefinedundefined

    let ask2 = yield "3 * 3 = ?"

    undefinedundefined

    alert(ask2); // 9 }

    undefinedundefined

    let generator = gen();

    undefinedundefined

    alert( generator.next().value ); // "2 + 2 = ?"

    undefinedundefined

    alert( generator.next(4).value ); // "3 * 3 = ?"

    undefinedundefined

    alert( generator.next(9).done ); // true

    undefinedundefined

    The execution picture:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined
      undefinedundefined
    1. The first undefinedundefined.next() starts the execution… It reaches the first undefinedundefinedyield.undefinedundefined
    2. undefinedundefined
    3. The result is returned to the outer code.
    4. undefinedundefined
    5. The second undefinedundefined.next(4) passes undefinedundefined4 back to the generator as the result of the first undefinedundefinedyield, and resumes the execution.undefinedundefined
    6. undefinedundefined
    7. …It reaches the second undefinedundefinedyield, that becomes the result of the generator call.undefinedundefined
    8. undefinedundefined
    9. The third undefinedundefinednext(9) passes undefinedundefined9 into the generator as the result of the second undefinedundefinedyield and resumes the execution that reaches the end of the function, so undefinedundefineddone: true.undefinedundefined
    10. undefinedundefined
    undefinedundefined

    It's like a "ping-pong" game. Each undefinedundefinednext(value) (excluding the first one) passes a value into the generator, that becomes the result of the current undefinedundefinedyield, and then gets back the result of the next undefinedundefinedyield.undefinedundefined

    undefinedundefined

    generator.throw

    undefinedundefined

    As we observed in the examples above, the outer code may pass a value into the generator, as the result of undefinedundefinedyield.undefinedundefined

    undefinedundefined

    …But it can also initiate (throw) an error there. That's natural, as an error is a kind of result.

    undefinedundefined

    To pass an error into a undefinedundefinedyield, we should call undefinedundefinedgenerator.throw(err). In that case, the undefinedundefinederr is thrown in the line with that undefinedundefinedyield.undefinedundefined

    undefinedundefined

    For instance, here the yield of undefinedundefined"2 + 2 = ?" leads to an error:undefinedundefined

    undefinedundefined

    run function* gen() { try { let result = yield "2 + 2 = ?"; // (1)

    undefinedundefined
    undefinedundefinedalert("The execution does not reach here, because the exception is thrown above");undefinedundefined
    undefinedundefined

    } catch(e) { alert(e); // shows the error } }

    undefinedundefined

    let generator = gen();

    undefinedundefined

    let question = generator.next().value;

    undefinedundefined

    undefinedundefined! generator.throw(new Error("The answer is not found in my database")); // (2) undefinedundefined/! undefinedundefined

    undefinedundefined

    The error, thrown into the generator at line undefinedundefined(2) leads to an exception in line undefinedundefined(1) with undefinedundefinedyield. In the example above, undefinedundefinedtry..catch catches it and shows it.undefinedundefined

    undefinedundefined

    If we don't catch it, then just like any exception, it "falls out" the generator into the calling code.

    undefinedundefined

    The current line of the calling code is the line with undefinedundefinedgenerator.throw, labelled as undefinedundefined(2). So we can catch it here, like this:undefinedundefined

    undefinedundefined

    run function* generate() { let result = yield "2 + 2 = ?"; // Error in this line }

    undefinedundefined

    let generator = generate();

    undefinedundefined

    let question = generator.next().value;

    undefinedundefined

    undefinedundefined! try { generator.throw(new Error("The answer is not found in my database")); } catch(e) { alert(e); // shows the error } undefinedundefined/! undefinedundefined

    undefinedundefined

    If we don't catch the error there, then, as usual, it falls through to the outer calling code (if any) and, if uncaught, kills the script.

    undefinedundefined

    Summary

    undefinedundefined
      undefinedundefined
    • Generators are created by generator functions undefinedundefinedfunction* f(…) {…}.undefinedundefined
    • undefinedundefined
    • Inside generators (only) there exists a undefinedundefinedyield operator.undefinedundefined
    • undefinedundefined
    • The outer code and the generator may exchange results via undefinedundefinednext/yield calls.undefinedundefined
    • undefinedundefined
    undefinedundefined

    In modern JavaScript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique. And, surely, they are great for making iterable objects.

    undefinedundefined

    Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data (e.g paginated fetches over a network) in undefinedundefinedfor await ... of loops.undefinedundefined

    undefinedundefined

    In web-programming we often work with streamed data, so that's another very important use case.

    undefinedundefined

    Async iteration and generators

    undefinedundefined

    Asynchronous iteration allow us to iterate over data that comes asynchronously, on-demand. Like, for instance, when we download something chunk-by-chunk over a network. And asynchronous generators make it even more convenient.

    undefinedundefined

    Let's see a simple example first, to grasp the syntax, and then review a real-life use case.

    undefinedundefined

    Recall iterables

    undefinedundefined

    Let's recall the topic about iterables.

    undefinedundefined

    The idea is that we have an object, such as undefinedundefinedrange here:undefinedundefined

    undefinedundefinedundefinedundefined

    …And we'd like to use undefinedundefinedfor..of loop on it, such as undefinedundefinedfor(value of range), to get values from undefinedundefined1 to undefinedundefined5.undefinedundefined

    undefinedundefined

    In other words, we want to add an undefinedundefinediteration ability to the object.undefinedundefined

    undefinedundefined

    That can be implemented using a special method with the name undefinedundefinedSymbol.iterator:undefinedundefined

    undefinedundefined
      undefinedundefined
    • This method is called in by the undefinedundefinedfor..of construct when the loop is started, and it should return an object with the undefinedundefinednext method.undefinedundefined
    • undefinedundefined
    • For each iteration, the undefinedundefinednext() method is invoked for the next value.undefinedundefined
    • undefinedundefined
    • The undefinedundefinednext() should return a value in the form undefinedundefined{done: true/false, value:<loop value>}, where undefinedundefineddone:true means the end of the loop.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Here's an implementation for the iterable undefinedundefinedrange:undefinedundefined

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    undefinedundefined!undefinedundefinedSymbol.iterator { // called once, in the beginning of for..of undefinedundefined/! return { current: this.from, last: this.to,undefinedundefined

    undefinedundefined

    undefinedundefined! next() { // called every iteration, to get the next value undefinedundefined/! if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } };undefinedundefined

    undefinedundefined

    for(let value of range) { alert(value); // 1 then 2, then 3, then 4, then 5 }

    undefinedundefined

    If anything is unclear, please visit the chapter undefinedundefined, it gives all the details about regular iterables.undefinedundefined

    undefinedundefined

    Async iterables

    undefinedundefined

    Asynchronous iteration is needed when values come asynchronously: after undefinedundefinedsetTimeout or another kind of delay.undefinedundefined

    undefinedundefined

    The most common case is that the object needs to make a network request to deliver the next value, we'll see a real-life example of it a bit later.

    undefinedundefined

    To make an object iterable asynchronously:

    undefinedundefined
      undefinedundefined
    1. Use undefinedundefinedSymbol.asyncIterator instead of undefinedundefinedSymbol.iterator.undefinedundefined
    2. undefinedundefined
    3. The undefinedundefinednext() method should return a promise (to be fulfilled with the next value). undefinedundefined
        undefinedundefined
      • The undefinedundefinedasync keyword handles it, we can simply make undefinedundefinedasync next().undefinedundefined
      • undefinedundefined
      undefinedundefined
    4. undefinedundefined
    5. To iterate over such an object, we should use a undefinedundefinedfor await (let item of iterable) loop. undefinedundefined
        undefinedundefined
      • Note the undefinedundefinedawait word.undefinedundefined
      • undefinedundefined
      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    As a starting example, let's make an iterable undefinedundefinedrange object, similar like the one before, but now it will return values asynchronously, one per second.undefinedundefined

    undefinedundefined

    All we need to do is to perform a few replacements in the code above:

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    undefinedundefined!undefinedundefinedSymbol.asyncIterator { // (1) undefinedundefined/! return { current: this.from, last: this.to,undefinedundefined

    undefinedundefined

    undefinedundefined! async next() { // (2) undefinedundefined/!undefinedundefined

    undefinedundefined

    undefinedundefined! // note: we can use "await" inside the async next: await new Promise(resolve => setTimeout(resolve, 1000)); // (3) undefinedundefined/!undefinedundefined

    undefinedundefined
    undefinedundefined    if (this.current <= this.last) {
    return { done: false, value: this.current++ };
    } else {
    return { done: true };
    }
    }
    };undefinedundefined
    undefinedundefined

    } };

    undefinedundefined

    (async () => {

    undefinedundefined

    undefinedundefined! for await (let value of range) { // (4) alert(value); // 1,2,3,4,5 } undefinedundefined/!undefinedundefined

    undefinedundefined

    })()

    undefinedundefined

    As we can see, the structure is similar to regular iterators:

    undefinedundefined
      undefinedundefined
    1. To make an object asynchronously iterable, it must have a method undefinedundefinedSymbol.asyncIteratorundefinedundefined(1).undefinedundefined
    2. undefinedundefined
    3. This method must return the object with undefinedundefinednext() method returning a promise undefinedundefined(2).undefinedundefined
    4. undefinedundefined
    5. The undefinedundefinednext() method doesn't have to be undefinedundefinedasync, it may be a regular method returning a promise, but undefinedundefinedasync allows us to use undefinedundefinedawait, so that's convenient. Here we just delay for a second undefinedundefined(3).undefinedundefined
    6. undefinedundefined
    7. To iterate, we use undefinedundefinedfor await(let value of range)undefinedundefined(4), namely add "await" after "for". It calls undefinedundefinedrange[Symbol.asyncIterator]() once, and then its undefinedundefinednext() for values.undefinedundefined
    8. undefinedundefined
    undefinedundefined

    Here's a small table with the differences:

    undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined
    IteratorsAsync iterators
    Object method to provide iterator undefinedundefinedSymbol.iteratorundefinedundefined undefinedundefinedSymbol.asyncIteratorundefinedundefined
    undefinedundefinednext() return value isundefinedundefinedany value undefinedundefinedPromiseundefinedundefined
    to loop, use undefinedundefinedfor..ofundefinedundefined undefinedundefinedfor await..ofundefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="The spread syntax…` doesn't work asynchronously" Features that require regular, synchronous iterators, don't work with asynchronous ones.undefinedundefined

    undefinedundefined

    For instance, a spread syntax won't work:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedalert( [...undefinedundefinedrange] )undefinedundefined;undefinedundefined// Error, no Symbol.iteratorundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    That's natural, as it expects to find undefinedundefinedSymbol.iterator, not undefinedundefinedSymbol.asyncIterator.undefinedundefined

    undefinedundefined

    It's also the case for undefinedundefinedfor..of: the syntax without undefinedundefinedawait needs undefinedundefinedSymbol.iterator. undefinedundefined

    undefinedundefined

    Recall generators

    undefinedundefined

    Now let's recall generators, as they allow to make iteration code much shorter. Most of the time, when we'd like to make an iterable, we'll use generators.

    undefinedundefined

    For sheer simplicity, omitting some important stuff, they are "functions that generate (yield) values". They are explained in detail in the chapter undefinedundefined.undefinedundefined

    undefinedundefined

    Generators are labelled with undefinedundefinedfunction* (note the star) and use undefinedundefinedyield to generate a value, then we can use undefinedundefinedfor..of to loop over them.undefinedundefined

    undefinedundefined

    This example generates a sequence of values from undefinedundefinedstart to undefinedundefinedend:undefinedundefined

    undefinedundefined

    run function* generateSequence(start, end) { for (let i = start; i <= end; i++) { yield i; } }

    undefinedundefined

    for(let value of generateSequence(1, 5)) { alert(value); // 1, then 2, then 3, then 4, then 5 }

    undefinedundefined

    As we already know, to make an object iterable, we should add undefinedundefinedSymbol.iterator to it.undefinedundefined

    undefinedundefinedundefinedundefined

    A common practice for undefinedundefinedSymbol.iterator is to return a generator, it makes the code shorter, as you can see:undefinedundefined

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    undefinedundefinedundefinedundefinedSymbol.iterator { // a shorthand for [Symbol.iterator]: functionundefinedundefined() for(let value = this.from; value <= this.to; value++) { yield value; } } };undefinedundefined

    undefinedundefined

    for(let value of range) { alert(value); // 1, then 2, then 3, then 4, then 5 }

    undefinedundefined

    Please see the chapter undefinedundefined if you'd like more details.undefinedundefined

    undefinedundefined

    In regular generators we can't use undefinedundefinedawait. All values must come synchronously, as required by the undefinedundefinedfor..of construct.undefinedundefined

    undefinedundefined

    What if we'd like to generate values asynchronously? From network requests, for instance.

    undefinedundefined

    Let's switch to asynchronous generators to make it possible.

    undefinedundefined

    Async generators (finally)

    undefinedundefined

    For most practical applications, when we'd like to make an object that asynchronously generates a sequence of values, we can use an asynchronous generator.

    undefinedundefined

    The syntax is simple: prepend undefinedundefinedfunction* with undefinedundefinedasync. That makes the generator asynchronous.undefinedundefined

    undefinedundefined

    And then use undefinedundefinedfor await (...) to iterate over it, like this:undefinedundefined

    undefinedundefined

    run undefinedundefined!asyncundefinedundefined/! function* generateSequence(start, end) {undefinedundefined

    undefinedundefined

    for (let i = start; i <= end; i++) {

    undefinedundefined

    undefinedundefined! // Wow, can use await! await new Promise(resolve => setTimeout(resolve, 1000)); undefinedundefined/!undefinedundefined

    undefinedundefined
    undefinedundefinedyield i;undefinedundefined
    undefinedundefined

    }

    undefinedundefined

    }

    undefinedundefined

    (async () => {

    undefinedundefined

    let generator = generateSequence(1, 5); for undefinedundefined!awaitundefinedundefined/! (let value of generator) { alert(value); // 1, then 2, then 3, then 4, then 5 (with delay between) }undefinedundefined

    undefinedundefined

    })();

    undefinedundefined

    As the generator is asynchronous, we can use undefinedundefinedawait inside it, rely on promises, perform network requests and so on.undefinedundefined

    undefinedundefined

    smart header="Under-the-hood difference" Technically, if you're an advanced reader who remembers the details about generators, there's an internal difference.

    undefinedundefined

    For async generators, the undefinedundefinedgenerator.next() method is asynchronous, it returns promises.undefinedundefined

    undefinedundefined

    In a regular generator we'd use undefinedundefinedresult = generator.next() to get values. In an async generator, we should add undefinedundefinedawait, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    That's why async generators work with undefinedundefinedfor await...of. undefinedundefined

    undefinedundefined

    Async iterable range

    undefinedundefined

    Regular generators can be used as undefinedundefinedSymbol.iterator to make the iteration code shorter.undefinedundefined

    undefinedundefined

    Similar to that, async generators can be used as undefinedundefinedSymbol.asyncIterator to implement the asynchronous iteration.undefinedundefined

    undefinedundefined

    For instance, we can make the undefinedundefinedrange object generate values asynchronously, once per second, by replacing synchronous undefinedundefinedSymbol.iterator with asynchronous undefinedundefinedSymbol.asyncIterator:undefinedundefined

    undefinedundefined

    run let range = { from: 1, to: 5,

    undefinedundefined

    // this line is same as [Symbol.asyncIterator]: async functionundefinedundefined() { !undefinedundefined async undefinedundefinedSymbol.asyncIterator { undefinedundefined/! for(let value = this.from; value <= this.to; value++) {undefinedundefined

    undefinedundefined
    undefinedundefined  // make a pause between values, wait for something  
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield value;
    }undefinedundefined
    undefinedundefined

    } };

    undefinedundefined

    (async () => {

    undefinedundefined

    for undefinedundefined!awaitundefinedundefined/! (let value of range) { alert(value); // 1, then 2, then 3, then 4, then 5 }undefinedundefined

    undefinedundefined

    })();

    undefinedundefined

    Now values come with a delay of 1 second between them.

    undefinedundefined
    undefinedundefinedTechnically, we can add both `Symbol.iterator` and `Symbol.asyncIterator` to the object, so it's both synchronously (`for..of`) and asynchronously (`for await..of`) iterable.
    In practice though, that would be a weird thing to do.undefinedundefined
    undefinedundefined

    Real-life example: paginated data

    undefinedundefined

    So far we've seen basic examples, to gain understanding. Now let's review a real-life use case.

    undefinedundefined

    There are many online services that deliver paginated data. For instance, when we need a list of users, a request returns a pre-defined count (e.g. 100 users) - "one page", and provides a URL to the next page.

    undefinedundefined

    This pattern is very common. It's not about users, but just about anything.

    undefinedundefined

    For instance, GitHub allows us to retrieve commits in the same, paginated fashion:

    undefinedundefined
      undefinedundefined
    • We should make a request to undefinedundefinedfetch in the form undefinedundefinedhttps://api.github.com/repos/<repo>/commits.undefinedundefined
    • undefinedundefined
    • It responds with a JSON of 30 commits, and also provides a link to the next page in the undefinedundefinedLink header.undefinedundefined
    • undefinedundefined
    • Then we can use that link for the next request, to get more commits, and so on.
    • undefinedundefined
    undefinedundefined

    For our code, we'd like to have a simpler way to get commits.

    undefinedundefined

    Let's make a function undefinedundefinedfetchCommits(repo) that gets commits for us, making requests whenever needed. And let it care about all pagination stuff. For us it'll be a simple async iteration undefinedundefinedfor await..of.undefinedundefined

    undefinedundefined

    So the usage will be like this:

    undefinedundefinedundefinedundefined

    Here's such function, implemented as async generator:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedasyncundefinedundefinedfunctionundefinedundefined*undefinedundefinedfetchCommits(repo) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedlet url undefinedundefined=undefinedundefined`https://api.github.com/repos/undefinedundefined${repoundefinedundefined}undefinedundefined/commits`undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedwhile (url) undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefinedconst response undefinedundefined=undefinedundefinedawaitundefinedundefinedfetch(urlundefinedundefined,undefinedundefined{undefinedundefined// (1)undefinedundefinedundefinedundefinedundefinedundefinedheadersundefinedundefined:undefinedundefined{undefinedundefined'User-Agent'undefinedundefined:undefinedundefined'Our script'undefinedundefined},undefinedundefined// github needs any user-agent headerundefinedundefinedundefinedundefinedundefinedundefined})undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedconst body undefinedundefined=undefinedundefinedawaitundefinedundefinedresponse.undefinedundefinedjson()undefinedundefined;undefinedundefined// (2) response is JSON (array of commits)undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// (3) the URL of the next page is in the headers, extract itundefinedundefinedundefinedundefinedundefinedundefinedlet nextPage undefinedundefined=undefinedundefinedresponse.undefinedundefinedheaders.undefinedundefinedget(undefinedundefined'Link').undefinedundefinedmatch(undefinedundefined/<undefinedundefined(undefinedundefined.undefinedundefined*?)undefinedundefined>; rel="next"/)undefinedundefined;undefinedundefinedundefinedundefined    nextPage undefinedundefined= nextPageundefinedundefined?.[undefinedundefined1]undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined    url undefinedundefined= nextPageundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedfor(undefinedundefinedlet commit undefinedundefinedof body) undefinedundefined{undefinedundefined// (4) yield commits one by one, until the page endsundefinedundefinedundefinedundefinedundefinedundefinedyield commitundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefined  }undefinedundefined}undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    More explanations about how it works:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      We use the browser undefinedundefinedfetch method to download the commits.undefinedundefined

      undefinedundefined
        undefinedundefined
      • The initial URL is undefinedundefinedhttps://api.github.com/repos/<repo>/commits, and the next page will be in the undefinedundefinedLink header of the response.undefinedundefined
      • undefinedundefined
      • The undefinedundefinedfetch method allows us to supply authorization and other headers if needed - here GitHub requires undefinedundefinedUser-Agent.undefinedundefined
      • undefinedundefined
      undefinedundefined
    2. undefinedundefined
    3. The commits are returned in JSON format.
    4. undefinedundefined
    5. We should get the next page URL from the undefinedundefinedLink header of the response. It has a special format, so we use a regular expression for that (we will learn this feature in undefinedundefinedRegular expressions). undefinedundefined
        undefinedundefined
      • The next page URL may look like undefinedundefinedhttps://api.github.com/repositories/93253246/commits?page=2. It's generated by GitHub itself.undefinedundefined
      • undefinedundefined
      undefinedundefined
    6. undefinedundefined
    7. undefinedundefined

      Then we yield the received commits one by one, and when they finish, the next undefinedundefinedwhile(url) iteration will trigger, making one more request.undefinedundefined

      undefinedundefined
    8. undefinedundefined
    undefinedundefined

    An example of use (shows commit authors in console):

    undefinedundefined

    run (async () => {

    undefinedundefined

    let count = 0;

    undefinedundefined

    for await (const commit of fetchCommits(‘javascript-tutorial/en.javascript.info')) {

    undefinedundefined
    undefinedundefinedconsole.log(commit.author.login);
    if (++count == 100) { // let's stop at 100 commits
    break;
    }undefinedundefined
    undefinedundefined

    }

    undefinedundefined

    })();

    undefinedundefined

    // Note: If you are running this in an external sandbox, you'll need to paste here the function fetchCommits described above

    undefinedundefined

    That's just what we wanted.

    undefinedundefined

    The internal mechanics of paginated requests is invisible from the outside. For us it's just an async generator that returns commits.

    undefinedundefined

    Summary

    undefinedundefined

    Regular iterators and generators work fine with the data that doesn't take time to generate.

    undefinedundefined

    When we expect the data to come asynchronously, with delays, their async counterparts can be used, and undefinedundefinedfor await..of instead of undefinedundefinedfor..of.undefinedundefined

    undefinedundefined

    Syntax differences between async and regular iterators:

    undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined
    IterableAsync Iterable
    Method to provide iteratorundefinedundefinedSymbol.iteratorundefinedundefined undefinedundefinedSymbol.asyncIteratorundefinedundefined
    undefinedundefinednext() return value isundefinedundefined undefinedundefined{value:…, done: true/false}undefinedundefined undefinedundefinedPromise that resolves to undefinedundefined{value:…, done: true/false}undefinedundefined
    undefinedundefined

    Syntax differences between async and regular generators:

    undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefined
    GeneratorsAsync generators
    Declarationundefinedundefinedfunction*undefinedundefined undefinedundefinedasync function*undefinedundefined
    undefinedundefinednext() return value isundefinedundefinedundefinedundefined{value:…, done: true/false}undefinedundefinedundefinedundefinedPromise that resolves to undefinedundefined{value:…, done: true/false}undefinedundefined
    undefinedundefined

    In web-development we often meet streams of data, when it flows chunk-by-chunk. For instance, downloading or uploading a big file.

    undefinedundefined

    We can use async generators to process such data. It's also noteworthy that in some environments, like in browsers, there's also another API called Streams, that provides special interfaces to work with such streams, to transform the data and to pass it from one stream to another (e.g. download from one place and immediately send elsewhere).

    undefinedundefined

    Modules, introduction

    undefinedundefined

    As our application grows bigger, we want to split it into multiple files, so called "modules". A module may contain a class or a library of functions for a specific purpose.

    undefinedundefined

    For a long time, JavaScript existed without a language-level module syntax. That wasn't a problem, because initially scripts were small and simple, so there was no need.

    undefinedundefined

    But eventually scripts became more and more complex, so the community invented a variety of ways to organize code into modules, special libraries to load modules on demand.

    undefinedundefined

    To name some (for historical reasons):

    undefinedundefined
      undefinedundefined
    • undefinedundefinedAMD - one of the most ancient module systems, initially implemented by the library undefinedundefinedrequire.js.undefinedundefined
    • undefinedundefined
    • undefinedundefinedCommonJS - the module system created for Node.js server.undefinedundefined
    • undefinedundefined
    • undefinedundefinedUMD - one more module system, suggested as a universal one, compatible with AMD and CommonJS.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Now all these slowly become a part of history, but we still can find them in old scripts.

    undefinedundefined

    The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js. So we'll study the modern JavaScript modules from now on.

    undefinedundefined

    What is a module?

    undefinedundefined

    A module is just a file. One script is one module. As simple as that.

    undefinedundefined

    Modules can load each other and use special directives undefinedundefinedexport and undefinedundefinedimport to interchange functionality, call functions of one module from another one:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedexport keyword labels variables and functions that should be accessible from outside the current module.undefinedundefined
    • undefinedundefined
    • undefinedundefinedimport allows the import of functionality from other modules.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance, if we have a file undefinedundefinedsayHi.js exporting a function:undefinedundefined

    undefinedundefinedundefinedundefined

    …Then another file may import and use it:

    undefinedundefinedundefinedundefined

    The undefinedundefinedimport directive loads the module by path undefinedundefined./sayHi.js relative to the current file, and assigns exported function undefinedundefinedsayHi to the corresponding variable.undefinedundefined

    undefinedundefined

    Let's run the example in-browser.

    undefinedundefined

    As modules support special keywords and features, we must tell the browser that a script should be treated as a module, by using the attribute undefinedundefined<script type="module">.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined

    [codetabs src="say" height="140" current="index.html"]

    undefinedundefined

    The browser automatically fetches and evaluates the imported module (and its imports if needed), and then runs the script.

    undefinedundefined

    undefinedundefinedwarn header="Modules work only via HTTP(s), not in local files" If you try to open a web-page locally, via `file://` protocol, you'll find that `import/export` directives don't work. Use a local web-server, such as [static-server](https://www.npmjs.com/package/static-server#getting-started) or use the "live server" capability of your editor, such as VS Code [Live Server Extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) to test modules.undefinedundefined

    undefinedundefined

    Core module features

    undefinedundefined

    What's different in modules, compared to "regular" scripts?

    undefinedundefined

    There are core features, valid both for browser and server-side JavaScript.

    undefinedundefined

    Always "use strict"

    undefinedundefined

    Modules always undefinedundefineduse strict, by default. E.g. assigning to an undeclared variable will give an error.undefinedundefined

    undefinedundefined

    undefinedundefinedhtml run <script type="module"> a = 5; // error </script>undefinedundefined

    undefinedundefined

    Module-level scope

    undefinedundefined

    Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts.

    undefinedundefined

    In the example below, two scripts are imported, and undefinedundefinedhello.js tries to use undefinedundefineduser variable declared in undefinedundefineduser.js, and fails:undefinedundefined

    undefinedundefined

    [codetabs src="scopes" height="140" current="index.html"]

    undefinedundefined

    Modules are expected to undefinedundefinedexport what they want to be accessible from outside and undefinedundefinedimport what they need.undefinedundefined

    undefinedundefined

    So we should import undefinedundefineduser.js into undefinedundefinedhello.js and get the required functionality from it instead of relying on global variables.undefinedundefined

    undefinedundefined

    This is the correct variant:

    undefinedundefined

    [codetabs src="scopes-working" height="140" current="hello.js"]

    undefinedundefined

    In the browser, independent top-level scope also exists for each undefinedundefined<script type="module">:undefinedundefined

    run undefinedundefined undefinedundefined undefinedundefined

    undefinedundefined

    If we really need to make a window-level global variable, we can explicitly assign it to undefinedundefinedwindow and access as undefinedundefinedwindow.user. But that's an exception requiring a good reason.undefinedundefined

    undefinedundefined

    A module code is evaluated only the first time when imported

    undefinedundefined

    If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers.

    undefinedundefined

    That has important consequences. Let's look at them using examples:

    undefinedundefined

    First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once - the first time:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// 📁 alert.jsundefinedundefinedundefinedundefinedundefinedundefinedalert(undefinedundefined"Module is evaluated!")undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefined

    In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable - export it.

    undefinedundefined

    Now, a more advanced example.

    undefinedundefined

    Let's say, a module exports an object:

    undefinedundefinedundefinedundefined

    If this module is imported from multiple files, the module is only evaluated the first time, undefinedundefinedadmin object is created, and then passed to all further importers.undefinedundefined

    undefinedundefined

    All importers get exactly the one and only undefinedundefinedadmin object:undefinedundefined

    undefinedundefinedundefinedundefined

    So, let's reiterate - the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the undefinedundefinedadmin object, other modules will see that.undefinedundefined

    undefinedundefined

    Such behavior allows us to undefinedundefinedconfigure modules on first import. We can setup its properties once, and then in further imports it's ready.undefinedundefined

    undefinedundefined

    For instance, the undefinedundefinedadmin.js module may provide certain functionality, but expect the credentials to come into the undefinedundefinedadmin object from outside:undefinedundefined

    undefinedundefinedundefinedundefined

    In undefinedundefinedinit.js, the first script of our app, we set undefinedundefinedadmin.name. Then everyone will see it, including calls made from inside undefinedundefinedadmin.js itself:undefinedundefined

    undefinedundefinedundefinedundefined

    Another module can also see undefinedundefinedadmin.name:undefinedundefined

    undefinedundefinedundefinedundefined

    import.meta

    undefinedundefined

    The object undefinedundefinedimport.meta contains the information about the current module.undefinedundefined

    undefinedundefined

    Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML:

    undefinedundefined

    undefinedundefinedhtml run height=0 <script type="module"> alert(import.meta.url); // script url (url of the html page for an inline script) </script>undefinedundefined

    undefinedundefined

    In a module, "this" is undefined

    undefinedundefined

    That's kind of a minor feature, but for completeness we should mention it.

    undefinedundefined

    In a module, top-level undefinedundefinedthis is undefined.undefinedundefined

    undefinedundefined

    Compare it to non-module scripts, where undefinedundefinedthis is a global object:undefinedundefined

    run height=0 undefinedundefined undefinedundefined undefinedundefined

    undefinedundefined

    Browser-specific features

    undefinedundefined

    There are also several browser-specific differences of scripts with undefinedundefinedtype="module" compared to regular ones.undefinedundefined

    undefinedundefined

    You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser.

    undefinedundefined

    Module scripts are deferred

    undefinedundefined

    Module scripts are undefinedundefinedalways deferred, same effect as undefinedundefineddefer attribute (described in the chapter undefinedundefined), for both external and inline scripts.undefinedundefined

    undefinedundefined

    In other words: - downloading external module scripts undefinedundefined<script type="module" src="..."> doesn't block HTML processing, they load in parallel with other resources. - module scripts wait until the HTML document is fully ready (even if they are tiny and load faster than HTML), and then run. - relative order of scripts is maintained: scripts that go first in the document, execute first.undefinedundefined

    undefinedundefined

    As a side-effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them.

    undefinedundefined

    For instance:

    run undefinedundefined undefinedundefined

    Compare to regular script below:

    undefinedundefined undefinedundefinedundefinedundefined

    undefinedundefined

    Please note: the second script actually runs before the first! So we'll see undefinedundefinedundefined first, and then undefinedundefinedobject.undefinedundefined

    undefinedundefined

    That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first.

    undefinedundefined

    When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that.

    undefinedundefined

    Async works on inline scripts

    undefinedundefined

    For non-module scripts, the undefinedundefinedasync attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document.undefinedundefined

    undefinedundefined

    For module scripts, it works on inline scripts as well.

    undefinedundefined

    For example, the inline script below has undefinedundefinedasync, so it doesn't wait for anything.undefinedundefined

    undefinedundefined

    It performs the import (fetches undefinedundefined./analytics.js) and runs when ready, even if the HTML document is not finished yet, or if other scripts are still pending.undefinedundefined

    undefinedundefined

    That's good for functionality that doesn't depend on anything, like counters, ads, document-level event listeners.

    undefinedundefinedundefinedundefined

    External scripts

    undefinedundefined

    External scripts that have undefinedundefinedtype="module" are different in two aspects:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      External scripts with the same undefinedundefinedsrc run only once: undefinedundefinedhtml <!-- the script my.js is fetched and executed only once --> <script type="module" src="my.js"></script> <script type="module" src="my.js"></script>undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      External scripts that are fetched from another origin (e.g. another site) require undefinedundefinedCORS headers, as described in the chapter undefinedundefinedinfo:fetch-crossorigin. In other words, if a module script is fetched from another origin, the remote server must supply a header undefinedundefinedAccess-Control-Allow-Origin allowing the fetch. undefinedundefinedhtml <!-- another-site.com must supply Access-Control-Allow-Origin --> <!-- otherwise, the script won't execute --> <script type="module" src="*!*http://another-site.com/their.js*/!*"></script>undefinedundefined

      undefinedundefined

      That ensures better security by default.

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    No "bare" modules allowed

    undefinedundefined

    In the browser, undefinedundefinedimport must get either a relative or absolute URL. Modules without any path are called "bare" modules. Such modules are not allowed in undefinedundefinedimport.undefinedundefined

    undefinedundefined

    For instance, this undefinedundefinedimport is invalid:undefinedundefined

    undefinedundefinedundefinedundefined

    Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet.

    undefinedundefined

    Compatibility, "nomodule"

    undefinedundefined

    Old browsers do not understand undefinedundefinedtype="module". Scripts of an unknown type are just ignored. For them, it's possible to provide a fallback using the undefinedundefinednomodule attribute:undefinedundefined

    run undefinedundefined undefinedundefined undefinedundefined

    undefinedundefined

    Build tools

    undefinedundefined

    In real-life, browser modules are rarely used in their "raw" form. Usually, we bundle them together with a special tool such as undefinedundefinedWebpack and deploy to the production server.undefinedundefined

    undefinedundefined

    One of the benefits of using bundlers - they give more control over how modules are resolved, allowing bare modules and much more, like CSS/HTML modules.

    undefinedundefined

    Build tools do the following:

    undefinedundefined
      undefinedundefined
    1. Take a "main" module, the one intended to be put in undefinedundefined<script type="module"> in HTML.undefinedundefined
    2. undefinedundefined
    3. Analyze its dependencies: imports and then imports of imports etc.
    4. undefinedundefined
    5. Build a single file with all modules (or multiple files, that's tunable), replacing native undefinedundefinedimport calls with bundler functions, so that it works. "Special" module types like HTML/CSS modules are also supported.undefinedundefined
    6. undefinedundefined
    7. In the process, other transformations and optimizations may be applied: undefinedundefined
        undefinedundefined
      • Unreachable code removed.
      • undefinedundefined
      • Unused exports removed ("tree-shaking").
      • undefinedundefined
      • Development-specific statements like undefinedundefinedconsole and undefinedundefineddebugger removed.undefinedundefined
      • undefinedundefined
      • Modern, bleeding-edge JavaScript syntax may be transformed to older one with similar functionality using undefinedundefinedBabel.undefinedundefined
      • undefinedundefined
      • The resulting file is minified (spaces removed, variables replaced with shorter names, etc).
      • undefinedundefined
      undefinedundefined
    8. undefinedundefined
    undefinedundefined

    If we use bundle tools, then as scripts are bundled together into a single file (or few files), undefinedundefinedimport/export statements inside those scripts are replaced by special bundler functions. So the resulting "bundled" script does not contain any undefinedundefinedimport/export, it doesn't require undefinedundefinedtype="module", and we can put it into a regular script:undefinedundefined

    undefinedundefinedundefinedundefined

    That said, native modules are also usable. So we won't be using Webpack here: you can configure it later.

    undefinedundefined

    Summary

    undefinedundefined

    To summarize, the core concepts are:

    undefinedundefined
      undefinedundefined
    1. A module is a file. To make undefinedundefinedimport/export work, browsers need undefinedundefined<script type="module">. Modules have several differences: undefinedundefined
        undefinedundefined
      • Deferred by default.
      • undefinedundefined
      • Async works on inline scripts.
      • undefinedundefined
      • To load external scripts from another origin (domain/protocol/port), CORS headers are needed.
      • undefinedundefined
      • Duplicate external scripts are ignored.
      • undefinedundefined
      undefinedundefined
    2. undefinedundefined
    3. Modules have their own, local top-level scope and interchange functionality via undefinedundefinedimport/export.undefinedundefined
    4. undefinedundefined
    5. Modules always undefinedundefineduse strict.undefinedundefined
    6. undefinedundefined
    7. Module code is executed only once. Exports are created once and shared between importers.
    8. undefinedundefined
    undefinedundefined

    When we use modules, each module implements the functionality and exports it. Then we use undefinedundefinedimport to directly import it where it's needed. The browser loads and evaluates the scripts automatically.undefinedundefined

    undefinedundefined

    In production, people often use bundlers such as undefinedundefinedWebpack to bundle modules together for performance and other reasons.undefinedundefined

    undefinedundefined

    In the next chapter we'll see more examples of modules, and how things can be exported/imported.

    undefinedundefined

    Export and Import

    undefinedundefined

    Export and import directives have several syntax variants.

    undefinedundefined

    In the previous article we saw a simple use, now let's explore more examples.

    undefinedundefined

    Export before declarations

    undefinedundefined

    We can label any declaration as exported by placing undefinedundefinedexport before it, be it a variable, function or a class.undefinedundefined

    undefinedundefined

    For instance, here all exports are valid:

    undefinedundefinedundefinedundefined

    undefinedundefinedsmart header="No semicolons after export class/function" Please note thatexport` before a class or a function does not make it a undefinedundefinedfunction expression. It's still a function declaration, albeit exported.undefinedundefined

    undefinedundefined

    Most JavaScript style guides don't recommend semicolons after function and class declarations.

    undefinedundefined

    That's why there's no need for a semicolon at the end of undefinedundefinedexport class and undefinedundefinedexport function:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Export apart from declarations

    undefinedundefined

    Also, we can put undefinedundefinedexport separately.undefinedundefined

    undefinedundefined

    Here we first declare, and then export:

    undefinedundefinedundefinedundefined

    …Or, technically we could put undefinedundefinedexport above functions as well.undefinedundefined

    undefinedundefined

    Import *

    undefinedundefined

    Usually, we put a list of what to import in curly braces undefinedundefinedimport {...}, like this:undefinedundefined

    undefinedundefinedundefinedundefined

    But if there's a lot to import, we can import everything as an object using undefinedundefinedimport * as <obj>, for instance:undefinedundefined

    undefinedundefinedundefinedundefined

    At first sight, "import everything" seems such a cool thing, short to write, why should we ever explicitly list what we need to import?

    undefinedundefined

    Well, there are few reasons.

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      Modern build tools (undefinedundefinedwebpack and others) bundle modules together and optimize them to speedup loading and remove unused stuff.undefinedundefined

      undefinedundefined

      Let's say, we added a 3rd-party library undefinedundefinedsay.js to our project with many functions: undefinedundefinedjs // 📁 say.js export function sayHi() { ... } export function sayBye() { ... } export function becomeSilent() { ... }undefinedundefined

      undefinedundefined

      Now if we only use one of undefinedundefinedsay.js functions in our project: undefinedundefinedjs // 📁 main.js import {sayHi} from './say.js'; …Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking".undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. Explicitly listing what to import gives shorter names: undefinedundefinedsayHi() instead of undefinedundefinedsay.sayHi().undefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier.

      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Import "as"

    undefinedundefined

    We can also use undefinedundefinedas to import under different names.undefinedundefined

    undefinedundefined

    For instance, let's import undefinedundefinedsayHi into the local variable undefinedundefinedhi for brevity, and import undefinedundefinedsayBye as undefinedundefinedbye:undefinedundefined

    undefinedundefined undefinedundefined

    Export "as"

    undefinedundefined

    The similar syntax exists for undefinedundefinedexport.undefinedundefined

    undefinedundefined

    Let's export functions as undefinedundefinedhi and undefinedundefinedbye:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// 📁 say.jsundefinedundefinedundefinedundefined...undefinedundefinedundefinedundefinedexportundefinedundefined{sayHi undefinedundefinedas hiundefinedundefined, sayBye undefinedundefinedas byeundefinedundefined};undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    Now undefinedundefinedhi and undefinedundefinedbye are official names for outsiders, to be used in imports:undefinedundefined

    undefinedundefinedundefinedundefined

    Export default

    undefinedundefined

    In practice, there are mainly two kinds of modules.

    undefinedundefined
      undefinedundefined
    1. Modules that contain a library, pack of functions, like undefinedundefinedsay.js above.undefinedundefined
    2. undefinedundefined
    3. Modules that declare a single entity, e.g. a module undefinedundefineduser.js exports only undefinedundefinedclass User.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Mostly, the second approach is preferred, so that every "thing" resides in its own module.

    undefinedundefined

    Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier if files are well-named and structured into folders.

    undefinedundefined

    Modules provide a special undefinedundefinedexport default ("the default export") syntax to make the "one thing per module" way look better.undefinedundefined

    undefinedundefined

    Put undefinedundefinedexport default before the entity to export:undefinedundefined

    undefinedundefinedundefinedundefined

    There may be only one undefinedundefinedexport default per file.undefinedundefined

    undefinedundefined

    …And then import it without curly braces:

    undefinedundefinedundefinedundefined

    Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, undefinedundefinedimport needs curly braces for named exports and doesn't need them for the default one.undefinedundefined

    undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined
    Named exportDefault export
    undefinedundefinedexport class User {...}undefinedundefinedundefinedundefinedexport default class User {...}undefinedundefined
    undefinedundefinedimport {User} from ...undefinedundefined undefinedundefinedimport User from ...undefinedundefined
    undefinedundefined

    Technically, we may have both default and named exports in a single module, but in practice people usually don't mix them. A module has either named exports or the default one.

    undefinedundefined

    As there may be at most one default export per file, the exported entity may have no name.

    undefinedundefined

    For instance, these are all perfectly valid default exports:

    undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined

    Not giving a name is fine, because there is only one undefinedundefinedexport default per file, so undefinedundefinedimport without curly braces knows what to import.undefinedundefined

    undefinedundefined

    Without undefinedundefineddefault, such an export would give an error:undefinedundefined

    undefinedundefinedundefinedundefined

    The "default" name

    undefinedundefined

    In some situations the undefinedundefineddefault keyword is used to reference the default export.undefinedundefined

    undefinedundefined

    For example, to export a function separately from its definition:

    undefinedundefinedundefinedundefined

    Or, another situation, let's say a module undefinedundefineduser.js exports one main "default" thing, and a few named ones (rarely the case, but it happens):undefinedundefined

    undefinedundefined undefinedundefined

    Here's how to import the default export along with a named one:

    undefinedundefinedundefinedundefined

    And, finally, if importing everything undefinedundefined* as an object, then the undefinedundefineddefault property is exactly the default export:undefinedundefined

    undefinedundefined undefinedundefined

    A word against default exports

    undefinedundefined

    Named exports are explicit. They exactly name what they import, so we have that information from them; that's a good thing.

    undefinedundefined

    Named exports force us to use exactly the right name to import:

    undefinedundefined undefinedundefined

    …While for a default export, we always choose the name when importing:

    undefinedundefined undefinedundefined

    So team members may use different names to import the same thing, and that's not good.

    undefinedundefined

    Usually, to avoid that and keep the code consistent, there's a rule that imported variables should correspond to file names, e.g:

    undefinedundefinedundefinedundefined

    Still, some teams consider it a serious drawback of default exports. So they prefer to always use named exports. Even if only a single thing is exported, it's still exported under a name, without undefinedundefineddefault.undefinedundefined

    undefinedundefined

    That also makes re-export (see below) a little bit easier.

    undefinedundefined

    Re-export

    undefinedundefined

    "Re-export" syntax undefinedundefinedexport ... from ... allows to import things and immediately export them (possibly under another name), like this:undefinedundefined

    undefinedundefinedundefinedundefined

    Why would that be needed? Let's see a practical use case.

    undefinedundefined

    Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules.

    undefinedundefined

    The file structure could be like this:

    undefinedundefined
    undefinedundefinedauth/
    index.js  
    user.js
    helpers.js
    tests/
    login.js
    providers/
    github.js
    facebook.js
    ...undefinedundefined
    undefinedundefined

    We'd like to expose the package functionality via a single entry point.

    undefinedundefined

    In other words, a person who would like to use our package, should import only from the "main file" undefinedundefinedauth/index.js.undefinedundefined

    undefinedundefined

    Like this:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedimportundefinedundefined{loginundefinedundefined, logoutundefinedundefined}undefinedundefinedfromundefinedundefined'auth/index.js'undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The "main file", undefinedundefinedauth/index.js exports all the functionality that we'd like to provide in our package.undefinedundefined

    undefinedundefined

    The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in undefinedundefinedauth/index.js and keep the rest hidden from prying eyes.undefinedundefined

    undefinedundefined

    As the actual exported functionality is scattered among the package, we can import it into undefinedundefinedauth/index.js and export from it:undefinedundefined

    undefinedundefinedundefinedundefined

    Now users of our package can undefinedundefinedimport {login} from "auth/index.js".undefinedundefined

    undefinedundefined

    The syntax undefinedundefinedexport ... from ... is just a shorter notation for such import-export:undefinedundefined

    undefinedundefinedundefinedundefined

    The notable difference of undefinedundefinedexport ... from compared to undefinedundefinedimport/export is that re-exported modules aren't available in the current file. So inside the above example of undefinedundefinedauth/index.js we can't use re-exported undefinedundefinedlogin/logout functions.undefinedundefined

    undefinedundefined

    Re-exporting the default export

    undefinedundefined

    The default export needs separate handling when re-exporting.

    undefinedundefined

    Let's say we have undefinedundefineduser.js with the undefinedundefinedexport default class User and would like to re-export it:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// 📁 user.jsundefinedundefinedundefinedundefinedundefinedundefinedexportundefinedundefineddefaultundefinedundefinedclass User undefinedundefined{undefinedundefinedundefinedundefinedundefinedundefined// ...undefinedundefinedundefinedundefinedundefinedundefined}undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    We can come across two problems with it:

    undefinedundefined
      undefinedundefined
    1. undefinedundefined

      undefinedundefinedexport User from './user.js' won't work. That would lead to a syntax error.undefinedundefined

      undefinedundefined

      To re-export the default export, we have to write undefinedundefinedexport {default as User}, as in the example above.undefinedundefined

      undefinedundefined
    2. undefinedundefined
    3. undefinedundefined

      undefinedundefinedexport * from './user.js' re-exports only named exports, but ignores the default one.undefinedundefined

      undefinedundefined

      If we'd like to re-export both named and the default export, then two statements are needed: undefinedundefinedjs export * from './user.js'; // to re-export named exports export {default} from './user.js'; // to re-export the default exportundefinedundefined

      undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones.

    undefinedundefined

    Summary

    undefinedundefined

    Here are all types of undefinedundefinedexport that we covered in this and previous articles.undefinedundefined

    undefinedundefined

    You can check yourself by reading them and recalling what they mean:

    undefinedundefined
      undefinedundefined
    • Before declaration of a class/function/..: undefinedundefined
        undefinedundefined
      • undefinedundefinedexport [default] class/function/variable ...undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Standalone export: undefinedundefined
        undefinedundefined
      • undefinedundefinedexport {x [as y], ...}.undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Re-export: undefinedundefined
        undefinedundefined
      • undefinedundefinedexport {x [as y], ...} from "module"undefinedundefined
      • undefinedundefined
      • undefinedundefinedexport * from "module" (doesn't re-export default).undefinedundefined
      • undefinedundefined
      • undefinedundefinedexport {default [as y]} from "module" (re-export default).undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    undefinedundefined

    Import:

    undefinedundefined
      undefinedundefined
    • Importing named exports: undefinedundefined
        undefinedundefined
      • undefinedundefinedimport {x [as y], ...} from "module"undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Importing the default export: undefinedundefined
        undefinedundefined
      • undefinedundefinedimport x from "module"undefinedundefined
      • undefinedundefined
      • undefinedundefinedimport {default as x} from "module"undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Import all: undefinedundefined
        undefinedundefined
      • undefinedundefinedimport * as obj from "module"undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    • Import the module (its code runs), but do not assign any of its exports to variables: undefinedundefined
        undefinedundefined
      • undefinedundefinedimport "module"undefinedundefined
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    undefinedundefined

    We can put undefinedundefinedimport/export statements at the top or at the bottom of a script, that doesn't matter.undefinedundefined

    undefinedundefined

    So, technically this code is fine:

    undefinedundefinedundefinedundefined

    In practice imports are usually at the start of the file, but that's only for more convenience.

    undefinedundefined

    undefinedundefinedPlease note that import/export statements don't work if inside undefinedundefined{...}.undefinedundefinedundefinedundefined

    undefinedundefined

    A conditional import, like this, won't work:

    undefinedundefinedundefinedundefined

    …But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it's really needed?

    undefinedundefined

    We'll see dynamic imports in the next article.

    undefinedundefined

    Dynamic imports

    undefinedundefined

    Export and import statements that we covered in previous chapters are called "static". The syntax is very simple and strict.

    undefinedundefined

    First, we can't dynamically generate any parameters of undefinedundefinedimport.undefinedundefined

    undefinedundefined

    The module path must be a primitive string, can't be a function call. This won't work:

    undefinedundefinedundefinedundefined

    Second, we can't import conditionally or at run-time:

    undefinedundefinedundefinedundefined

    That's because undefinedundefinedimport/undefinedundefinedexport aim to provide a backbone for the code structure. That's a good thing, as code structure can be analyzed, modules can be gathered and bundled into one file by special tools, unused exports can be removed ("tree-shaken"). That's possible only because the structure of imports/exports is simple and fixed.undefinedundefined

    undefinedundefined

    But how can we import a module dynamically, on-demand?

    undefinedundefined

    The import() expression

    undefinedundefined

    The undefinedundefinedimport(module) expression loads the module and returns a promise that resolves into a module object that contains all its exports. It can be called from any place in the code.undefinedundefined

    undefinedundefined

    We can use it dynamically in any place of the code, for instance:

    undefinedundefinedundefinedundefined

    Or, we could use undefinedundefinedlet module = await import(modulePath) if inside an async function.undefinedundefined

    undefinedundefined

    For instance, if we have the following module undefinedundefinedsay.js:undefinedundefined

    undefinedundefinedundefinedundefined

    …Then dynamic import can be like this:

    undefinedundefinedundefinedundefined

    Or, if undefinedundefinedsay.js has the default export:undefinedundefined

    undefinedundefinedundefinedundefined

    …Then, in order to access it, we can use undefinedundefineddefault property of the module object:undefinedundefined

    undefinedundefinedundefinedundefined

    Here's the full example:

    undefinedundefined

    [codetabs src="say" current="index.html"]

    undefinedundefined
    undefinedundefinedDynamic imports work in regular scripts, they don't require `script type="module"`.undefinedundefined
    undefinedundefined
    undefinedundefinedAlthough `import()` looks like a function call, it's a special syntax that just happens to use parentheses (similar to `super()`).
    So we can't copy `import` to a variable or use `call/apply` with it. It's not a function.undefinedundefined
    undefinedundefined

    Proxy and Reflect

    undefinedundefined

    A undefinedundefinedProxy object wraps another object and intercepts operations, like reading/writing properties and others, optionally handling them on its own, or transparently allowing the object to handle them.undefinedundefined

    undefinedundefined

    Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this article.

    undefinedundefined

    Proxy

    undefinedundefined

    The syntax:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet proxy undefinedundefined=undefinedundefinednewundefinedundefinedProxy(targetundefinedundefined, handler)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
      undefinedundefined
    • undefinedundefinedtarget - is an object to wrap, can be anything, including functions.undefinedundefined
    • undefinedundefined
    • undefinedundefinedhandler - proxy configuration: an object with "traps", methods that intercept operations. - e.g. undefinedundefinedget trap for reading a property of undefinedundefinedtarget, undefinedundefinedset trap for writing a property into undefinedundefinedtarget, and so on.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For operations on undefinedundefinedproxy, if there's a corresponding trap in undefinedundefinedhandler, then it runs, and the proxy has a chance to handle it, otherwise the operation is performed on undefinedundefinedtarget.undefinedundefined

    undefinedundefined

    As a starting example, let's create a proxy without any traps:

    undefinedundefined

    run let target = {}; let proxy = new Proxy(target, {}); // empty handler

    undefinedundefined

    proxy.test = 5; // writing to proxy (1) alert(target.test); // 5, the property appeared in target!

    undefinedundefined

    alert(proxy.test); // 5, we can read it from proxy too (2)

    undefinedundefined

    for(let key in proxy) alert(key); // test, iteration works (3)

    undefinedundefined

    As there are no traps, all operations on undefinedundefinedproxy are forwarded to undefinedundefinedtarget.undefinedundefined

    undefinedundefined
      undefinedundefined
    1. A writing operation undefinedundefinedproxy.test= sets the value on undefinedundefinedtarget.undefinedundefined
    2. undefinedundefined
    3. A reading operation undefinedundefinedproxy.test returns the value from undefinedundefinedtarget.undefinedundefined
    4. undefinedundefined
    5. Iteration over undefinedundefinedproxy returns values from undefinedundefinedtarget.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    As we can see, without any traps, undefinedundefinedproxy is a transparent wrapper around undefinedundefinedtarget.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    undefinedundefinedProxy is a special "exotic object". It doesn't have own properties. With an empty undefinedundefinedhandler it transparently forwards operations to undefinedundefinedtarget.undefinedundefined

    undefinedundefined

    To activate more capabilities, let's add traps.

    undefinedundefined

    What can we intercept with them?

    undefinedundefined

    For most operations on objects, there's a so-called "internal method" in the JavaScript specification that describes how it works at the lowest level. For instance undefinedundefined[[Get]], the internal method to read a property, undefinedundefined[[Set]], the internal method to write a property, and so on. These methods are only used in the specification, we can't call them directly by name.undefinedundefined

    undefinedundefined

    Proxy traps intercept invocations of these methods. They are listed in the undefinedundefinedProxy specification and in the table below.undefinedundefined

    undefinedundefined

    For every internal method, there's a trap in this table: the name of the method that we can add to the undefinedundefinedhandler parameter of undefinedundefinednew Proxy to intercept the operation:undefinedundefined

    undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined
    Internal MethodHandler MethodTriggers when…
    undefinedundefined[[Get]]undefinedundefined undefinedundefinedgetundefinedundefinedreading a property
    undefinedundefined[[Set]]undefinedundefined undefinedundefinedsetundefinedundefinedwriting to a property
    undefinedundefined[[HasProperty]]undefinedundefined undefinedundefinedhasundefinedundefinedundefinedundefinedin operatorundefinedundefined
    undefinedundefined[[Delete]]undefinedundefined undefinedundefineddeletePropertyundefinedundefined undefinedundefineddelete operatorundefinedundefined
    undefinedundefined[[Call]]undefinedundefinedundefinedundefinedapplyundefinedundefinedfunction call
    undefinedundefined[[Construct]]undefinedundefined undefinedundefinedconstructundefinedundefined undefinedundefinednew operatorundefinedundefined
    undefinedundefined[[GetPrototypeOf]]undefinedundefinedundefinedundefinedgetPrototypeOfundefinedundefined undefinedundefinedObject.getPrototypeOfundefinedundefined
    undefinedundefined[[SetPrototypeOf]]undefinedundefined undefinedundefinedsetPrototypeOfundefinedundefinedundefinedundefinedObject.setPrototypeOfundefinedundefined
    undefinedundefined[[IsExtensible]]undefinedundefined undefinedundefinedisExtensibleundefinedundefinedundefinedundefinedObject.isExtensibleundefinedundefined
    undefinedundefined[[PreventExtensions]]undefinedundefined undefinedundefinedpreventExtensionsundefinedundefined undefinedundefinedObject.preventExtensionsundefinedundefined
    undefinedundefined[[DefineOwnProperty]]undefinedundefined undefinedundefineddefinePropertyundefinedundefinedundefinedundefinedObject.defineProperty, undefinedundefinedObject.definePropertiesundefinedundefined
    undefinedundefined[[GetOwnProperty]]undefinedundefined undefinedundefinedgetOwnPropertyDescriptorundefinedundefined undefinedundefinedObject.getOwnPropertyDescriptor, undefinedundefinedfor..in, undefinedundefinedObject.keys/values/entriesundefinedundefined
    undefinedundefined[[OwnPropertyKeys]]undefinedundefined undefinedundefinedownKeysundefinedundefinedundefinedundefinedObject.getOwnPropertyNames, undefinedundefinedObject.getOwnPropertySymbols, undefinedundefinedfor..in, undefinedundefinedObject.keys/values/entriesundefinedundefined
    undefinedundefined

    warn header="Invariants" JavaScript enforces some invariants - conditions that must be fulfilled by internal methods and traps.

    undefinedundefined

    Most of them are for return values: - undefinedundefined[[Set]] must return undefinedundefinedtrue if the value was written successfully, otherwise undefinedundefinedfalse. - undefinedundefined[[Delete]] must return undefinedundefinedtrue if the value was deleted successfully, otherwise undefinedundefinedfalse. - …and so on, we'll see more in examples below.undefinedundefined

    undefinedundefined

    There are some other invariants, like: - undefinedundefined[[GetPrototypeOf]], applied to the proxy object must return the same value as undefinedundefined[[GetPrototypeOf]] applied to the proxy object's target object. In other words, reading prototype of a proxy must always return the prototype of the target object.undefinedundefined

    undefinedundefined

    Traps can intercept these operations, but they must follow these rules.

    undefinedundefined

    Invariants ensure correct and consistent behavior of language features. The full invariants list is in undefinedundefinedthe specification. You probably won't violate them if you're not doing something weird. undefinedundefined

    undefinedundefined

    Let's see how that works in practical examples.

    undefinedundefined

    Default value with "get" trap

    undefinedundefined

    The most common traps are for reading/writing properties.

    undefinedundefined

    To intercept reading, the undefinedundefinedhandler should have a method undefinedundefinedget(target, property, receiver).undefinedundefined

    undefinedundefined

    It triggers when a property is read, with following arguments:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedtarget - is the target object, the one passed as the first argument to undefinedundefinednew Proxy,undefinedundefined
    • undefinedundefined
    • undefinedundefinedproperty - property name,undefinedundefined
    • undefinedundefined
    • undefinedundefinedreceiver - if the target property is a getter, then undefinedundefinedreceiver is the object that's going to be used as undefinedundefinedthis in its call. Usually that's the undefinedundefinedproxy object itself (or an object that inherits from it, if we inherit from proxy). Right now we don't need this argument, so it will be explained in more detail later.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Let's use undefinedundefinedget to implement default values for an object.undefinedundefined

    undefinedundefined

    We'll make a numeric array that returns undefinedundefined0 for nonexistent values.undefinedundefined

    undefinedundefined

    Usually when one tries to get a non-existing array item, they get undefinedundefinedundefined, but we'll wrap a regular array into the proxy that traps reading and returns undefinedundefined0 if there's no such property:undefinedundefined

    undefinedundefined

    run let numbers = [0, 1, 2];

    undefinedundefined

    numbers = new Proxy(numbers, { get(target, prop) { if (prop in target) { return target[prop]; } else { return 0; // default value } } });

    undefinedundefined

    undefinedundefined! alert( numbers[1] ); // 1 alert( numbers[123] ); // 0 (no such item) undefinedundefined/! undefinedundefined

    undefinedundefined

    As we can see, it's quite easy to do with a undefinedundefinedget trap.undefinedundefined

    undefinedundefined

    We can use undefinedundefinedProxy to implement any logic for "default" values.undefinedundefined

    undefinedundefined

    Imagine we have a dictionary, with phrases and their translations:

    undefinedundefined

    run let dictionary = { ‘Hello': ‘Hola', ‘Bye': ‘Adiós' };

    undefinedundefined

    alert( dictionary[‘Hello'] ); // Hola alert( dictionary[‘Welcome'] ); // undefined

    undefinedundefined

    Right now, if there's no phrase, reading from undefinedundefineddictionary returns undefinedundefinedundefined. But in practice, leaving a phrase untranslated is usually better than undefinedundefinedundefined. So let's make it return an untranslated phrase in that case instead of undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    To achieve that, we'll wrap undefinedundefineddictionary in a proxy that intercepts reading operations:undefinedundefined

    undefinedundefined

    run let dictionary = { ‘Hello': ‘Hola', ‘Bye': ‘Adiós' };

    undefinedundefined

    dictionary = new Proxy(dictionary, { undefinedundefined! get(target, phrase) { // intercept reading a property from dictionary undefinedundefined/! if (phrase in target) { // if we have it in the dictionary return target[phrase]; // return the translation } else { // otherwise, return the non-translated phrase return phrase; } } });undefinedundefined

    undefinedundefined

    // Look up arbitrary phrases in the dictionary! // At worst, they're not translated. alert( dictionary[‘Hello'] ); // Hola undefinedundefined! alert( dictionary[‘Welcome to Proxy']); // Welcome to Proxy (no translation) undefinedundefined/! undefinedundefined

    undefinedundefined
    undefinedundefinedPlease note how the proxy overwrites the variable:
    dictionary = new Proxy(dictionary, ...);
    The proxy should totally replace the target object everywhere. No one should ever reference the target object after it got proxied. Otherwise it's easy to mess up.undefinedundefined
    undefinedundefined

    Validation with "set" trap

    undefinedundefined

    Let's say we want an array exclusively for numbers. If a value of another type is added, there should be an error.

    undefinedundefined

    The undefinedundefinedset trap triggers when a property is written.undefinedundefined

    undefinedundefined

    undefinedundefinedset(target, property, value, receiver):undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedtarget - is the target object, the one passed as the first argument to undefinedundefinednew Proxy,undefinedundefined
    • undefinedundefined
    • undefinedundefinedproperty - property name,undefinedundefined
    • undefinedundefined
    • undefinedundefinedvalue - property value,undefinedundefined
    • undefinedundefined
    • undefinedundefinedreceiver - similar to undefinedundefinedget trap, matters only for setter properties.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The undefinedundefinedset trap should return undefinedundefinedtrue if setting is successful, and undefinedundefinedfalse otherwise (triggers undefinedundefinedTypeError).undefinedundefined

    undefinedundefined

    Let's use it to validate new values:

    undefinedundefined

    run let numbers = [];

    undefinedundefined

    numbers = new Proxy(numbers, { // (undefinedundefined) !undefinedundefined set(target, prop, val) { // to intercept property writing /!* if (typeof val == ‘number') { target[prop] = val; return true; } else { return false; } } });undefinedundefined

    undefinedundefined

    numbers.push(1); // added successfully numbers.push(2); // added successfully alert("Length is:" + numbers.length); // 2

    undefinedundefined

    undefinedundefined! numbers.push("test"); // TypeError (‘set' on proxy returned false) undefinedundefined/!undefinedundefined

    undefinedundefined

    alert("This line is never reached (error in the line above)");

    undefinedundefined

    Please note: the built-in functionality of arrays is still working! Values are added by undefinedundefinedpush. The undefinedundefinedlength property auto-increases when values are added. Our proxy doesn't break anything.undefinedundefined

    undefinedundefined

    We don't have to override value-adding array methods like undefinedundefinedpush and undefinedundefinedunshift, and so on, to add checks in there, because internally they use the undefinedundefined[[Set]] operation that's intercepted by the proxy.undefinedundefined

    undefinedundefined

    So the code is clean and concise.

    undefinedundefined

    ``undefinedundefinedwarn header="Don't forget to returntrue`" As said above, there are invariants to be held.undefinedundefined

    undefinedundefined

    For undefinedundefinedset, it must return undefinedundefinedtrue for a successful write.undefinedundefined

    undefinedundefined

    If we forget to do it or return any falsy value, the operation triggers undefinedundefinedTypeError. undefinedundefined

    undefinedundefined

    Iteration with "ownKeys" and "getOwnPropertyDescriptor"

    undefinedundefined

    undefinedundefinedObject.keys, undefinedundefinedfor..in loop and most other methods that iterate over object properties use undefinedundefined[[OwnPropertyKeys]] internal method (intercepted by undefinedundefinedownKeys trap) to get a list of properties.undefinedundefined

    undefinedundefined

    Such methods differ in details: - undefinedundefinedObject.getOwnPropertyNames(obj) returns non-symbol keys. - undefinedundefinedObject.getOwnPropertySymbols(obj) returns symbol keys. - undefinedundefinedObject.keys/values() returns non-symbol keys/values with undefinedundefinedenumerable flag (property flags were explained in the article undefinedundefinedinfo:property-descriptors). - undefinedundefinedfor..in loops over non-symbol keys with undefinedundefinedenumerable flag, and also prototype keys.undefinedundefined

    undefinedundefined

    …But all of them start with that list.

    undefinedundefined

    In the example below we use undefinedundefinedownKeys trap to make undefinedundefinedfor..in loop over undefinedundefineduser, and also undefinedundefinedObject.keys and undefinedundefinedObject.values, to skip properties starting with an underscore undefinedundefined_:undefinedundefined

    undefinedundefined

    run let user = { name: "John", age: 30, _password: "***" };

    undefinedundefined

    user = new Proxy(user, { undefinedundefined! ownKeys(target) { undefinedundefined/! return Object.keys(target).filter(key => !key.startsWith('_')); } });undefinedundefined

    undefinedundefined

    // "ownKeys" filters out _password for(let key in user) alert(key); // name, then: age

    undefinedundefined

    // same effect on these methods: alert( Object.keys(user) ); // name,age alert( Object.values(user) ); // John,30

    undefinedundefined

    So far, it works.

    undefinedundefined

    Although, if we return a key that doesn't exist in the object, undefinedundefinedObject.keys won't list it:undefinedundefined

    undefinedundefined

    run let user = { };

    undefinedundefined

    user = new Proxy(user, { undefinedundefined! ownKeys(target) { undefinedundefined/! return [‘a', ‘b', ‘c']; } });undefinedundefined

    undefinedundefined

    alert( Object.keys(user) ); // undefinedundefined undefinedundefined

    undefinedundefined

    Why? The reason is simple: undefinedundefinedObject.keys returns only properties with the undefinedundefinedenumerable flag. To check for it, it calls the internal method undefinedundefined[[GetOwnProperty]] for every property to get undefinedundefinedits descriptor. And here, as there's no property, its descriptor is empty, no undefinedundefinedenumerable flag, so it's skipped.undefinedundefined

    undefinedundefined

    For undefinedundefinedObject.keys to return a property, we need it to either exist in the object, with the undefinedundefinedenumerable flag, or we can intercept calls to undefinedundefined[[GetOwnProperty]] (the trap undefinedundefinedgetOwnPropertyDescriptor does it), and return a descriptor with undefinedundefinedenumerable: true.undefinedundefined

    undefinedundefined

    Here's an example of that:

    undefinedundefined

    run let user = { };

    undefinedundefined

    user = new Proxy(user, { ownKeys(target) { // called once to get a list of properties return [‘a', ‘b', ‘c']; },

    undefinedundefined

    getOwnPropertyDescriptor(target, prop) { // called for every property return { enumerable: true, configurable: true /* …other flags, probable "value:…" */ }; }

    undefinedundefined

    });

    undefinedundefined

    alert( Object.keys(user) ); // a, b, c

    undefinedundefined

    Let's note once again: we only need to intercept undefinedundefined[[GetOwnProperty]] if the property is absent in the object.undefinedundefined

    undefinedundefined

    Protected properties with "deleteProperty" and other traps

    undefinedundefined

    There's a widespread convention that properties and methods prefixed by an underscore undefinedundefined_ are internal. They shouldn't be accessed from outside the object.undefinedundefined

    undefinedundefined

    Technically that's possible though:

    undefinedundefined

    run let user = { name: "John", _password: "secret" };

    undefinedundefined

    alert(user._password); // secret

    undefinedundefined

    Let's use proxies to prevent any access to properties starting with undefinedundefined_.undefinedundefined

    undefinedundefined

    We'll need the traps: - undefinedundefinedget to throw an error when reading such property, - undefinedundefinedset to throw an error when writing, - undefinedundefineddeleteProperty to throw an error when deleting, - undefinedundefinedownKeys to exclude properties starting with undefinedundefined_ from undefinedundefinedfor..in and methods like undefinedundefinedObject.keys.undefinedundefined

    undefinedundefined

    Here's the code:

    undefinedundefined

    run let user = { name: "John", _password: "***" };

    undefinedundefined

    user = new Proxy(user, { undefinedundefined! get(target, prop) { undefinedundefined/! if (prop.startsWith('_‘)) { throw new Error("Access denied"); } let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (undefinedundefined) }, !undefinedundefined set(target, prop, val) { // to intercept property writing /!undefinedundefined if (prop.startsWith(‘undefinedundefined''')) { throw new Error("Access denied"); } else { target[prop] = val; return true; } }, undefinedundefined! deleteProperty(target, prop) { // to intercept property deletion undefinedundefined/! if (prop.startsWith('undefinedundefined''')) { throw new Error("Access denied"); } else { delete target[prop]; return true; } }, undefinedundefined!undefinedundefined ownKeys(target) { // to intercept property list /!* return Object.keys(target).filter(key => !key.startsWith('_')); } });undefinedundefined

    undefinedundefined

    // "get" doesn't allow to read _password try { alert(user._password); // Error: Access denied } catch(e) { alert(e.message); }

    undefinedundefined

    // "set" doesn't allow to write _password try { user._password = "test"; // Error: Access denied } catch(e) { alert(e.message); }

    undefinedundefined

    // "deleteProperty" doesn't allow to delete _password try { delete user._password; // Error: Access denied } catch(e) { alert(e.message); }

    undefinedundefined

    // "ownKeys" filters out _password for(let key in user) alert(key); // name

    undefinedundefined

    Please note the important detail in the undefinedundefinedget trap, in the line undefinedundefined(*):undefinedundefined

    undefinedundefinedundefinedundefined

    Why do we need a function to call undefinedundefinedvalue.bind(target)?undefinedundefined

    undefinedundefined

    The reason is that object methods, such as undefinedundefineduser.checkPassword(), must be able to access undefinedundefined_password:undefinedundefined

    undefinedundefinedundefinedundefined

    A call to undefinedundefineduser.checkPassword() gets proxied undefinedundefineduser as undefinedundefinedthis (the object before dot becomes undefinedundefinedthis), so when it tries to access undefinedundefinedthis._password, the undefinedundefinedget trap activates (it triggers on any property read) and throws an error.undefinedundefined

    undefinedundefined

    So we bind the context of object methods to the original object, undefinedundefinedtarget, in the line undefinedundefined(*). Then their future calls will use undefinedundefinedtarget as undefinedundefinedthis, without any traps.undefinedundefined

    undefinedundefined

    That solution usually works, but isn't ideal, as a method may pass the unproxied object somewhere else, and then we'll get messed up: where's the original object, and where's the proxied one?

    undefinedundefined

    Besides, an object may be proxied multiple times (multiple proxies may add different "tweaks" to the object), and if we pass an unwrapped object to a method, there may be unexpected consequences.

    undefinedundefined

    So, such a proxy shouldn't be used everywhere.

    undefinedundefined

    ``undefinedundefinedsmart header="Private properties of a class" Modern JavaScript engines natively support private properties in classes, prefixed with#`. They are described in the article undefinedundefinedinfo:private-protected-properties-methods. No proxies required.undefinedundefined

    undefinedundefined

    Such properties have their own issues though. In particular, they are not inherited.

    undefinedundefined

    "In range" with "has" trap

    undefinedundefined

    Let's see more examples.

    undefinedundefined

    We have a range object:

    undefinedundefinedundefinedundefined

    We'd like to use the undefinedundefinedin operator to check that a number is in undefinedundefinedrange.undefinedundefined

    undefinedundefined

    The undefinedundefinedhas trap intercepts undefinedundefinedin calls.undefinedundefined

    undefinedundefined

    undefinedundefinedhas(target, property)undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedtarget - is the target object, passed as the first argument to undefinedundefinednew Proxy,undefinedundefined
    • undefinedundefined
    • undefinedundefinedproperty - property nameundefinedundefined
    • undefinedundefined
    undefinedundefined

    Here's the demo:

    undefinedundefined

    run let range = { start: 1, end: 10 };

    undefinedundefined

    range = new Proxy(range, { undefinedundefined! has(target, prop) { undefinedundefined/! return prop >= target.start && prop <= target.end; } });undefinedundefined

    undefinedundefined

    undefinedundefined! alert(5 in range); // true alert(50 in range); // false undefinedundefined/! undefinedundefined

    undefinedundefined

    Nice syntactic sugar, isn't it? And very simple to implement.

    undefinedundefined

    Wrapping functions: "apply" [#proxy-apply]

    undefinedundefined

    We can wrap a proxy around a function as well.

    undefinedundefined

    The undefinedundefinedapply(target, thisArg, args) trap handles calling a proxy as function:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedtarget is the target object (function is an object in JavaScript),undefinedundefined
    • undefinedundefined
    • undefinedundefinedthisArg is the value of undefinedundefinedthis.undefinedundefined
    • undefinedundefined
    • undefinedundefinedargs is a list of arguments.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For example, let's recall undefinedundefineddelay(f, ms) decorator, that we did in the article undefinedundefinedinfo:call-apply-decorators.undefinedundefined

    undefinedundefined

    In that article we did it without proxies. A call to undefinedundefineddelay(f, ms) returned a function that forwards all calls to undefinedundefinedf after undefinedundefinedms milliseconds.undefinedundefined

    undefinedundefined

    Here's the previous, function-based implementation:

    undefinedundefined

    run function delay(f, ms) { // return a wrapper that passes the call to f after the timeout return function() { // (*) setTimeout(() => f.apply(this, arguments), ms); }; }

    undefinedundefined

    function sayHi(user) { alert(undefinedundefinedHello, ${user}!); }undefinedundefined

    undefinedundefined

    // after this wrapping, calls to sayHi will be delayed for 3 seconds sayHi = delay(sayHi, 3000);

    undefinedundefined

    sayHi("John"); // Hello, John! (after 3 seconds)

    undefinedundefined

    As we've seen already, that mostly works. The wrapper function undefinedundefined(*) performs the call after the timeout.undefinedundefined

    undefinedundefined

    But a wrapper function does not forward property read/write operations or anything else. After the wrapping, the access is lost to properties of the original functions, such as undefinedundefinedname, undefinedundefinedlength and others:undefinedundefined

    undefinedundefined

    run function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; }

    undefinedundefined

    function sayHi(user) { alert(undefinedundefinedHello, ${user}!); }undefinedundefined

    undefinedundefined

    undefinedundefined! alert(sayHi.length); // 1 (function length is the arguments count in its declaration) undefinedundefined/!undefinedundefined

    undefinedundefined

    sayHi = delay(sayHi, 3000);

    undefinedundefined

    undefinedundefined! alert(sayHi.length); // 0 (in the wrapper declaration, there are zero arguments) undefinedundefined/! undefinedundefined

    undefinedundefined

    undefinedundefinedProxy is much more powerful, as it forwards everything to the target object.undefinedundefined

    undefinedundefined

    Let's use undefinedundefinedProxy instead of a wrapping function:undefinedundefined

    undefinedundefined

    run function delay(f, ms) { return new Proxy(f, { apply(target, thisArg, args) { setTimeout(() => target.apply(thisArg, args), ms); } }); }

    undefinedundefined

    function sayHi(user) { alert(undefinedundefinedHello, ${user}!); }undefinedundefined

    undefinedundefined

    sayHi = delay(sayHi, 3000);

    undefinedundefined

    undefinedundefined! alert(sayHi.length); // 1 (undefinedundefined) proxy forwards "get length" operation to the target /!*undefinedundefined

    undefinedundefined

    sayHi("John"); // Hello, John! (after 3 seconds)

    undefinedundefined

    The result is the same, but now not only calls, but all operations on the proxy are forwarded to the original function. So undefinedundefinedsayHi.length is returned correctly after the wrapping in the line undefinedundefined(*).undefinedundefined

    undefinedundefined

    We've got a "richer" wrapper.

    undefinedundefined

    Other traps exist: the full list is in the beginning of this article. Their usage pattern is similar to the above.

    undefinedundefined

    Reflect

    undefinedundefined

    undefinedundefinedReflect is a built-in object that simplifies creation of undefinedundefinedProxy.undefinedundefined

    undefinedundefined

    It was said previously that internal methods, such as undefinedundefined[[Get]], undefinedundefined[[Set]] and others are specification-only, they can't be called directly.undefinedundefined

    undefinedundefined

    The undefinedundefinedReflect object makes that somewhat possible. Its methods are minimal wrappers around the internal methods.undefinedundefined

    undefinedundefined

    Here are examples of operations and undefinedundefinedReflect calls that do the same:undefinedundefined

    undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    OperationundefinedundefinedReflect callundefinedundefinedInternal method
    undefinedundefinedobj[prop]undefinedundefined undefinedundefinedReflect.get(obj, prop)undefinedundefined undefinedundefined[[Get]]undefinedundefined
    undefinedundefinedobj[prop] = valueundefinedundefinedundefinedundefinedReflect.set(obj, prop, value)undefinedundefinedundefinedundefined[[Set]]undefinedundefined
    undefinedundefineddelete obj[prop]undefinedundefined undefinedundefinedReflect.deleteProperty(obj, prop)undefinedundefined undefinedundefined[[Delete]]undefinedundefined
    undefinedundefinednew F(value)undefinedundefinedundefinedundefinedReflect.construct(F, value)undefinedundefinedundefinedundefined[[Construct]]undefinedundefined
    undefinedundefined

    For example:

    undefinedundefined

    run let user = {};

    undefinedundefined

    Reflect.set(user, ‘name', ‘John');

    undefinedundefined

    alert(user.name); // John

    undefinedundefined

    In particular, undefinedundefinedReflect allows us to call operators (undefinedundefinednew, undefinedundefineddelete…) as functions (undefinedundefinedReflect.construct, undefinedundefinedReflect.deleteProperty, …). That's an interesting capability, but here another thing is important.undefinedundefined

    undefinedundefined

    undefinedundefinedFor every internal method, trappable by undefinedundefinedProxy, there's a corresponding method in undefinedundefinedReflect, with the same name and arguments as the undefinedundefinedProxy trap.undefinedundefinedundefinedundefined

    undefinedundefined

    So we can use undefinedundefinedReflect to forward an operation to the original object.undefinedundefined

    undefinedundefined

    In this example, both traps undefinedundefinedget and undefinedundefinedset transparently (as if they didn't exist) forward reading/writing operations to the object, showing a message:undefinedundefined

    undefinedundefined

    run let user = { name: "John", };

    undefinedundefined

    user = new Proxy(user, { get(target, prop, receiver) { alert(undefinedundefinedGET ${prop}); undefinedundefined! return Reflect.get(target, prop, receiver); // (1) undefinedundefined/! }, set(target, prop, val, receiver) { alert(undefinedundefinedSET ${prop}=${val}); undefinedundefined! return Reflect.set(target, prop, val, receiver); // (2) undefinedundefined/! } });undefinedundefined

    undefinedundefined

    let name = user.name; // shows "GET name" user.name = "Pete"; // shows "SET name=Pete"

    undefinedundefined

    Here:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedReflect.get reads an object property.undefinedundefined
    • undefinedundefined
    • undefinedundefinedReflect.set writes an object property and returns undefinedundefinedtrue if successful, undefinedundefinedfalse otherwise.undefinedundefined
    • undefinedundefined
    undefinedundefined

    That is, everything's simple: if a trap wants to forward the call to the object, it's enough to call undefinedundefinedReflect.<method> with the same arguments.undefinedundefined

    undefinedundefined

    In most cases we can do the same without undefinedundefinedReflect, for instance, reading a property undefinedundefinedReflect.get(target, prop, receiver) can be replaced by undefinedundefinedtarget[prop]. There are important nuances though.undefinedundefined

    undefinedundefined

    Proxying a getter

    undefinedundefined

    Let's see an example that demonstrates why undefinedundefinedReflect.get is better. And we'll also see why undefinedundefinedget/set have the third argument undefinedundefinedreceiver, that we didn't use before.undefinedundefined

    undefinedundefined

    We have an object undefinedundefineduser with undefinedundefined_name property and a getter for it.undefinedundefined

    undefinedundefined

    Here's a proxy around it:

    undefinedundefined

    run let user = { _name: "Guest", get name() { return this._name; } };

    undefinedundefined

    undefinedundefined! let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; } }); undefinedundefined/!undefinedundefined

    undefinedundefined

    alert(userProxy.name); // Guest

    undefinedundefined

    The undefinedundefinedget trap is "transparent" here, it returns the original property, and doesn't do anything else. That's enough for our example.undefinedundefined

    undefinedundefined

    Everything seems to be all right. But let's make the example a little bit more complex.

    undefinedundefined

    After inheriting another object undefinedundefinedadmin from undefinedundefineduser, we can observe the incorrect behavior:undefinedundefined

    undefinedundefined

    run let user = { _name: "Guest", get name() { return this._name; } };

    undefinedundefined

    let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; // (*) target = user } });

    undefinedundefined

    undefinedundefined! let admin = { undefinedundefinedproto: userProxy, _name: "Admin" };undefinedundefined

    undefinedundefined

    // Expected: Admin alert(admin.name); // outputs: Guest (?!?) undefinedundefined/! undefinedundefined

    undefinedundefined

    Reading undefinedundefinedadmin.name should return undefinedundefined"Admin", not undefinedundefined"Guest"!undefinedundefined

    undefinedundefined

    What's the matter? Maybe we did something wrong with the inheritance?

    undefinedundefined

    But if we remove the proxy, then everything will work as expected.

    undefinedundefined

    The problem is actually in the proxy, in the line undefinedundefined(*).undefinedundefined

    undefinedundefined
      undefinedundefined
    1. When we read undefinedundefinedadmin.name, as undefinedundefinedadmin object doesn't have such own property, the search goes to its prototype.undefinedundefined
    2. undefinedundefined
    3. The prototype is undefinedundefineduserProxy.undefinedundefined
    4. undefinedundefined
    5. undefinedundefined

      When reading undefinedundefinedname property from the proxy, its undefinedundefinedget trap triggers and returns it from the original object as undefinedundefinedtarget[prop] in the line undefinedundefined(*).undefinedundefined

      undefinedundefined

      A call to undefinedundefinedtarget[prop], when undefinedundefinedprop is a getter, runs its code in the context undefinedundefinedthis=target. So the result is undefinedundefinedthis._name from the original object undefinedundefinedtarget, that is: from undefinedundefineduser.undefinedundefined

      undefinedundefined
    6. undefinedundefined
    undefinedundefined

    To fix such situations, we need undefinedundefinedreceiver, the third argument of undefinedundefinedget trap. It keeps the correct undefinedundefinedthis to be passed to a getter. In our case that's undefinedundefinedadmin.undefinedundefined

    undefinedundefined

    How to pass the context for a getter? For a regular function we could use undefinedundefinedcall/apply, but that's a getter, it's not "called", just accessed.undefinedundefined

    undefinedundefined

    undefinedundefinedReflect.get can do that. Everything will work right if we use it.undefinedundefined

    undefinedundefined

    Here's the corrected variant:

    undefinedundefined

    run let user = { _name: "Guest", get name() { return this._name; } };

    undefinedundefined

    let userProxy = new Proxy(user, { get(target, prop, receiver) { // receiver = admin undefinedundefined! return Reflect.get(target, prop, receiver); // (undefinedundefined) /!* } });undefinedundefined

    undefinedundefined

    let admin = { undefinedundefinedproto: userProxy, _name: "Admin" };undefinedundefined

    undefinedundefined

    undefinedundefined! alert(admin.name); // Admin undefinedundefined/! undefinedundefined

    undefinedundefined

    Now undefinedundefinedreceiver that keeps a reference to the correct undefinedundefinedthis (that is undefinedundefinedadmin), is passed to the getter using undefinedundefinedReflect.get in the line undefinedundefined(*).undefinedundefined

    undefinedundefined

    We can rewrite the trap even shorter:

    undefinedundefinedundefinedundefined

    undefinedundefinedReflect calls are named exactly the same way as traps and accept the same arguments. They were specifically designed this way.undefinedundefined

    undefinedundefined

    So, undefinedundefinedreturn Reflect... provides a safe no-brainer to forward the operation and make sure we don't forget anything related to that.undefinedundefined

    undefinedundefined

    Proxy limitations

    undefinedundefined

    Proxies provide a unique way to alter or tweak the behavior of the existing objects at the lowest level. Still, it's not perfect. There are limitations.

    undefinedundefined

    Built-in objects: Internal slots

    undefinedundefined

    Many built-in objects, for example undefinedundefinedMap, undefinedundefinedSet, undefinedundefinedDate, undefinedundefinedPromise and others make use of so-called "internal slots".undefinedundefined

    undefinedundefined

    These are like properties, but reserved for internal, specification-only purposes. For instance, undefinedundefinedMap stores items in the internal slot undefinedundefined[[MapData]]. Built-in methods access them directly, not via undefinedundefined[[Get]]/[[Set]] internal methods. So undefinedundefinedProxy can't intercept that.undefinedundefined

    undefinedundefined

    Why care? They're internal anyway!

    undefinedundefined

    Well, here's the issue. After a built-in object like that gets proxied, the proxy doesn't have these internal slots, so built-in methods will fail.

    undefinedundefined

    For example:

    undefinedundefined

    run let map = new Map();

    undefinedundefined

    let proxy = new Proxy(map, {});

    undefinedundefined

    undefinedundefined! proxy.set(‘test', 1); // Error undefinedundefined/! undefinedundefined

    undefinedundefined

    Internally, a undefinedundefinedMap stores all data in its undefinedundefined[[MapData]] internal slot. The proxy doesn't have such a slot. The undefinedundefinedbuilt-in method undefinedundefinedMap.prototype.setundefinedundefined method tries to access the internal property undefinedundefinedthis.[[MapData]], but because undefinedundefinedthis=proxy, can't find it in undefinedundefinedproxy and just fails.undefinedundefined

    undefinedundefined

    Fortunately, there's a way to fix it:

    undefinedundefined

    run let map = new Map();

    undefinedundefined

    let proxy = new Proxy(map, { get(target, prop, receiver) { let value = Reflect.get(…arguments); undefinedundefined! return typeof value == ‘function' ? value.bind(target) : value; undefinedundefined/! } });undefinedundefined

    undefinedundefined

    proxy.set(‘test', 1); alert(proxy.get(‘test')); // 1 (works!)

    undefinedundefined

    Now it works fine, because undefinedundefinedget trap binds function properties, such as undefinedundefinedmap.set, to the target object (undefinedundefinedmap) itself.undefinedundefined

    undefinedundefined

    Unlike the previous example, the value of undefinedundefinedthis inside undefinedundefinedproxy.set(...) will be not undefinedundefinedproxy, but the original undefinedundefinedmap. So when the internal implementation of undefinedundefinedset tries to access undefinedundefinedthis.[[MapData]] internal slot, it succeeds.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Arrayundefinedundefinedhas no internal slots" A notable exception: built-inArray` doesn't use internal slots. That's for historical reasons, as it appeared so long ago.undefinedundefined

    undefinedundefined

    So there's no such problem when proxying an array.

    undefinedundefined

    Private fields

    undefinedundefined

    A similar thing happens with private class fields.

    undefinedundefined

    For example, undefinedundefinedgetName() method accesses the private undefinedundefined#name property and breaks after proxying:undefinedundefined

    undefinedundefined

    run class User { #name = "Guest";

    undefinedundefined

    getName() { return this.#name; } }

    undefinedundefined

    let user = new User();

    undefinedundefined

    user = new Proxy(user, {});

    undefinedundefined

    undefinedundefined! alert(user.getName()); // Error undefinedundefined/! undefinedundefined

    undefinedundefined

    The reason is that private fields are implemented using internal slots. JavaScript does not use undefinedundefined[[Get]]/[[Set]] when accessing them.undefinedundefined

    undefinedundefined

    In the call undefinedundefinedgetName() the value of undefinedundefinedthis is the proxied undefinedundefineduser, and it doesn't have the slot with private fields.undefinedundefined

    undefinedundefined

    Once again, the solution with binding the method makes it work:

    undefinedundefined

    run class User { #name = "Guest";

    undefinedundefined

    getName() { return this.#name; } }

    undefinedundefined

    let user = new User();

    undefinedundefined

    user = new Proxy(user, { get(target, prop, receiver) { let value = Reflect.get(…arguments); return typeof value == ‘function' ? value.bind(target) : value; } });

    undefinedundefined

    alert(user.getName()); // Guest

    undefinedundefined

    That said, the solution has drawbacks, as explained previously: it exposes the original object to the method, potentially allowing it to be passed further and breaking other proxied functionality.

    undefinedundefined

    Proxy != target

    undefinedundefined

    The proxy and the original object are different objects. That's natural, right?

    undefinedundefined

    So if we use the original object as a key, and then proxy it, then the proxy can't be found:

    undefinedundefined

    run let allUsers = new Set();

    undefinedundefined

    class User { constructor(name) { this.name = name; allUsers.add(this); } }

    undefinedundefined

    let user = new User("John");

    undefinedundefined

    alert(allUsers.has(user)); // true

    undefinedundefined

    user = new Proxy(user, {});

    undefinedundefined

    undefinedundefined! alert(allUsers.has(user)); // false undefinedundefined/! undefinedundefined

    undefinedundefined

    As we can see, after proxying we can't find undefinedundefineduser in the set undefinedundefinedallUsers, because the proxy is a different object.undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="Proxies can't intercept a strict equality test===undefinedundefined" Proxies can intercept many operators, such asnewundefinedundefined(withconstructundefinedundefined),inundefinedundefined(withhasundefinedundefined),deleteundefinedundefined(withdeleteProperty`) and so on.undefinedundefined

    undefinedundefined

    But there's no way to intercept a strict equality test for objects. An object is strictly equal to itself only, and no other value.

    undefinedundefined

    So all operations and built-in classes that compare objects for equality will differentiate between the object and the proxy. No transparent replacement here.

    undefinedundefined

    Revocable proxies

    undefinedundefined

    A undefinedundefinedrevocable proxy is a proxy that can be disabled.undefinedundefined

    undefinedundefined

    Let's say we have a resource, and would like to close access to it any moment.

    undefinedundefined

    What we can do is to wrap it into a revocable proxy, without any traps. Such a proxy will forward operations to object, and we can disable it at any moment.

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    The call returns an object with the undefinedundefinedproxy and undefinedundefinedrevoke function to disable it.undefinedundefined

    undefinedundefined

    Here's an example:

    undefinedundefined

    run let object = { data: "Valuable data" };

    undefinedundefined

    let {proxy, revoke} = Proxy.revocable(object, {});

    undefinedundefined

    // pass the proxy somewhere instead of object… alert(proxy.data); // Valuable data

    undefinedundefined

    // later in our code revoke();

    undefinedundefined

    // the proxy isn't working any more (revoked) alert(proxy.data); // Error

    undefinedundefined

    A call to undefinedundefinedrevoke() removes all internal references to the target object from the proxy, so they are no longer connected.undefinedundefined

    undefinedundefined

    Initially, undefinedundefinedrevoke is separate from undefinedundefinedproxy, so that we can pass undefinedundefinedproxy around while leaving undefinedundefinedrevoke in the current scope.undefinedundefined

    undefinedundefined

    We can also bind undefinedundefinedrevoke method to proxy by setting undefinedundefinedproxy.revoke = revoke.undefinedundefined

    undefinedundefined

    Another option is to create a undefinedundefinedWeakMap that has undefinedundefinedproxy as the key and the corresponding undefinedundefinedrevoke as the value, that allows to easily find undefinedundefinedrevoke for a proxy:undefinedundefined

    undefinedundefined

    run undefinedundefined! let revokes = new WeakMap(); undefinedundefined/!undefinedundefined

    undefinedundefined

    let object = { data: "Valuable data" };

    undefinedundefined

    let {proxy, revoke} = Proxy.revocable(object, {});

    undefinedundefined

    revokes.set(proxy, revoke);

    undefinedundefined

    // ..somewhere else in our code.. revoke = revokes.get(proxy); revoke();

    undefinedundefined

    alert(proxy.data); // Error (revoked)

    undefinedundefined

    We use undefinedundefinedWeakMap instead of undefinedundefinedMap here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), undefinedundefinedWeakMap allows it to be wiped from memory together with its undefinedundefinedrevoke that we won't need any more.undefinedundefined

    undefinedundefined

    References

    undefinedundefined
      undefinedundefined
    • Specification: undefinedundefinedProxy.undefinedundefined
    • undefinedundefined
    • MDN: undefinedundefinedProxy.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedProxy is a wrapper around an object, that forwards operations on it to the object, optionally trapping some of them.undefinedundefined

    undefinedundefined

    It can wrap any kind of object, including classes and functions.

    undefinedundefined

    The syntax is:

    undefinedundefinedundefinedundefined

    …Then we should use undefinedundefinedproxy everywhere instead of undefinedundefinedtarget. A proxy doesn't have its own properties or methods. It traps an operation if the trap is provided, otherwise forwards it to undefinedundefinedtarget object.undefinedundefined

    undefinedundefined

    We can trap: - Reading (undefinedundefinedget), writing (undefinedundefinedset), deleting (undefinedundefineddeleteProperty) a property (even a non-existing one). - Calling a function (undefinedundefinedapply trap). - The undefinedundefinednew operator (undefinedundefinedconstruct trap). - Many other operations (the full list is at the beginning of the article and in the undefinedundefineddocs).undefinedundefined

    undefinedundefined

    That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more.

    undefinedundefined

    We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality.

    undefinedundefined

    The undefinedundefinedReflect API is designed to complement undefinedundefinedProxy. For any undefinedundefinedProxy trap, there's a undefinedundefinedReflect call with same arguments. We should use those to forward calls to target objects.undefinedundefined

    undefinedundefined

    Proxies have some limitations:

    undefinedundefined
      undefinedundefined
    • Built-in objects have "internal slots", access to those can't be proxied. See the workaround above.
    • undefinedundefined
    • The same holds true for private class fields, as they are internally implemented using slots. So proxied method calls must have the target object as undefinedundefinedthis to access them.undefinedundefined
    • undefinedundefined
    • Object equality tests undefinedundefined=== can't be intercepted.undefinedundefined
    • undefinedundefined
    • Performance: benchmarks depend on an engine, but generally accessing a property using a simplest proxy takes a few times longer. In practice that only matters for some "bottleneck" objects though.
    • undefinedundefined
    undefinedundefined

    Eval: run a code string

    undefinedundefined

    The built-in undefinedundefinedeval function allows to execute a string of code.undefinedundefined

    undefinedundefined

    The syntax is:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet result undefinedundefined=undefinedundefinedeval(code)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    For example:

    undefinedundefined

    undefinedundefinedjs run let code = 'alert("Hello")'; eval(code); // Helloundefinedundefined

    undefinedundefined

    A string of code may be long, contain line breaks, function declarations, variables and so on.

    undefinedundefined

    The result of undefinedundefinedeval is the result of the last statement.undefinedundefined

    undefinedundefined

    For example: undefinedundefinedjs run let value = eval('1+1'); alert(value); // 2undefinedundefined

    undefinedundefined

    undefinedundefinedjs run let value = eval('let i = 0; ++i'); alert(value); // 1undefinedundefined

    undefinedundefined

    The eval'ed code is executed in the current lexical environment, so it can see outer variables:

    undefinedundefined

    run no-beautify let a = 1;

    undefinedundefined

    function f() { let a = 2;

    undefinedundefined

    undefinedundefined! eval(‘alert(a)'''); // 2 undefinedundefined/! }undefinedundefined

    undefinedundefined

    f();

    undefinedundefined

    It can change outer variables as well:

    undefinedundefined

    undefinedundefinedjs untrusted refresh run let x = 5; eval("x = 10"); alert(x); // 10, value modifiedundefinedundefined

    undefinedundefined

    In strict mode, undefinedundefinedeval has its own lexical environment. So functions and variables, declared inside eval, are not visible outside:undefinedundefined

    undefinedundefined

    untrusted refresh run // reminder: ‘use strict' is enabled in runnable examples by default

    undefinedundefined

    eval("let x = 5; function f() {}");

    undefinedundefined

    alert(typeof x); // undefined (no such variable) // function f is also not visible

    undefinedundefined

    Without undefinedundefineduse strict, undefinedundefinedeval doesn't have its own lexical environment, so we would see undefinedundefinedx and undefinedundefinedf outside.undefinedundefined

    undefinedundefined

    Using "eval"

    undefinedundefined

    In modern programming undefinedundefinedeval is used very sparingly. It's often said that "eval is evil".undefinedundefined

    undefinedundefined

    The reason is simple: long, long time ago JavaScript was a much weaker language, many things could only be done with undefinedundefinedeval. But that time passed a decade ago.undefinedundefined

    undefinedundefined

    Right now, there's almost no reason to use undefinedundefinedeval. If someone is using it, there's a good chance they can replace it with a modern language construct or a undefinedundefinedJavaScript Module.undefinedundefined

    undefinedundefined

    Please note that its ability to access outer variables has side-effects.

    undefinedundefined

    Code minifiers (tools used before JS gets to production, to compress it) rename local variables into shorter ones (like undefinedundefineda, undefinedundefinedb etc) to make the code smaller. That's usually safe, but not if undefinedundefinedeval is used, as local variables may be accessed from eval'ed code string. So minifiers don't do that renaming for all variables potentially visible from undefinedundefinedeval. That negatively affects code compression ratio.undefinedundefined

    undefinedundefined

    Using outer local variables inside undefinedundefinedeval is also considered a bad programming practice, as it makes maintaining the code more difficult.undefinedundefined

    undefinedundefined

    There are two ways how to be totally safe from such problems.

    undefinedundefined

    undefinedundefinedIf eval'ed code doesn't use outer variables, please call undefinedundefinedeval as undefinedundefinedwindow.eval(...):undefinedundefinedundefinedundefined

    undefinedundefined

    This way the code is executed in the global scope:

    undefinedundefined

    undefinedundefinedjs untrusted refresh run let x = 1; { let x = 5; window.eval('alert(x)'); // 1 (global variable) }undefinedundefined

    undefinedundefined

    undefinedundefinedIf eval'ed code needs local variables, change undefinedundefinedeval to undefinedundefinednew Function and pass them as arguments:undefinedundefinedundefinedundefined

    undefinedundefined

    run let f = new Function(‘a', ‘alert(a)''');

    undefinedundefined

    f(5); // 5

    undefinedundefined

    The undefinedundefinednew Function construct is explained in the chapter undefinedundefinedinfo:new-function. It creates a function from a string, also in the global scope. So it can't see local variables. But it's so much clearer to pass them explicitly as arguments, like in the example above.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    A call to undefinedundefinedeval(code) runs the string of code and returns the result of the last statement. - Rarely used in modern JavaScript, as there's usually no need. - Can access outer local variables. That's considered bad practice. - Instead, to undefinedundefinedeval the code in the global scope, use undefinedundefinedwindow.eval(code). - Or, if your code needs some data from the outer scope, use undefinedundefinednew Function and pass it as arguments.undefinedundefined

    undefinedundefined

    Data types

    undefinedundefined

    A value in JavaScript is always of a certain type. For example, a string or a number.

    undefinedundefined

    There are eight basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail.

    undefinedundefined

    We can put any type in a variable. For example, a variable can at one moment be a string and then store a number:

    undefinedundefined undefinedundefined

    Programming languages that allow such things, such as JavaScript, are called "dynamically typed", meaning that there exist data types, but variables are not bound to any of them.

    undefinedundefined

    Number

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet n undefinedundefined=undefinedundefined123undefinedundefined;undefinedundefinedundefinedundefinedn undefinedundefined=undefinedundefined12.345undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The undefinedundefinednumber type represents both integer and floating point numbers.undefinedundefined

    undefinedundefined

    There are many operations for numbers, e.g. multiplication undefinedundefined*, division undefinedundefined/, addition undefinedundefined+, subtraction undefinedundefined-, and so on.undefinedundefined

    undefinedundefined

    Besides regular numbers, there are so-called "special numeric values" which also belong to this data type: undefinedundefinedInfinity, undefinedundefined-Infinity and undefinedundefinedNaN.undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefined

      undefinedundefinedInfinity represents the mathematical undefinedundefinedInfinity ∞. It is a special value that's greater than any number.undefinedundefined

      undefinedundefined

      We can get it as a result of division by zero:

      undefinedundefined

      undefinedundefinedjs run alert( 1 / 0 ); // Infinityundefinedundefined

      undefinedundefined

      Or just reference it directly:

      undefinedundefinedjs run alert( Infinity ); // Infinityundefinedundefined
    • undefinedundefined
    • undefinedundefined

      undefinedundefinedNaN represents a computational error. It is a result of an incorrect or an undefined mathematical operation, for instance:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run alert( "not a number" / 2 ); // NaN, such division is erroneousundefinedundefined

      undefinedundefined

      undefinedundefinedNaN is sticky. Any further operation on undefinedundefinedNaN returns undefinedundefinedNaN:undefinedundefined

      undefinedundefined

      undefinedundefinedjs run alert( "not a number" / 2 + 5 ); // NaNundefinedundefined

      undefinedundefined

      So, if there's a undefinedundefinedNaN somewhere in a mathematical expression, it propagates to the whole result.undefinedundefined

      undefinedundefined
    • undefinedundefined
    undefinedundefined

    smart header="Mathematical operations are safe" Doing maths is "safe" in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc.

    undefinedundefined

    The script will never stop with a fatal error ("die"). At worst, we'll get undefinedundefinedNaN as the result. undefinedundefined

    undefinedundefined

    Special numeric values formally belong to the "number" type. Of course they are not numbers in the common sense of this word.

    undefinedundefined

    We'll see more about working with numbers in the chapter undefinedundefinedinfo:number.undefinedundefined

    undefinedundefined

    BigInt

    undefinedundefined

    In JavaScript, the "number" type cannot represent integer values larger than undefinedundefined(2undefinedundefined53-1)undefinedundefined (that's undefinedundefined9007199254740991), or less than undefinedundefined-(2undefinedundefined53-1)undefinedundefined for negatives. It's a technical limitation caused by their internal representation.undefinedundefined

    undefinedundefined

    For most purposes that's quite enough, but sometimes we need really big numbers, e.g. for cryptography or microsecond-precision timestamps.

    undefinedundefined

    undefinedundefinedBigInt type was recently added to the language to represent integers of arbitrary length.undefinedundefined

    undefinedundefined

    A undefinedundefinedBigInt value is created by appending undefinedundefinedn to the end of an integer:undefinedundefined

    undefinedundefinedundefinedundefined

    As undefinedundefinedBigInt numbers are rarely needed, we don't cover them here, but devoted them a separate chapter undefinedundefinedinfo:bigint. Read it when you need such big numbers.undefinedundefined

    undefinedundefined

    undefinedundefinedsmart header="Compatibility issues" Right now, `BigInt` is supported in Firefox/Chrome/Edge/Safari, but not in IE.undefinedundefined

    undefinedundefined

    You can check undefinedundefinedundefinedundefinedMDN BigInt compatibility tableundefinedundefined to know which versions of a browser are supported.undefinedundefined

    undefinedundefined

    String

    undefinedundefined

    A string in JavaScript must be surrounded by quotes.

    undefinedundefinedundefinedundefined

    In JavaScript, there are 3 types of quotes.

    undefinedundefined
      undefinedundefined
    1. Double quotes: undefinedundefined"Hello".undefinedundefined
    2. undefinedundefined
    3. Single quotes: undefinedundefined'Hello'.undefinedundefined
    4. undefinedundefined
    5. Backticks: undefinedundefined`Hello`.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript.

    undefinedundefined

    Backticks are "extended functionality" quotes. They allow us to embed variables and expressions into a string by wrapping them in undefinedundefined${…}, for example:undefinedundefined

    undefinedundefined

    run let name = "John";

    undefinedundefined

    // embed a variable alert( undefinedundefinedHello, *!*${name}*/!*! ); // Hello, John!undefinedundefined

    undefinedundefined

    // embed an expression alert( undefinedundefinedthe result is *!*${1 + 2}*/!* ); // the result is 3 undefinedundefined

    undefinedundefined

    The expression inside undefinedundefined${…} is evaluated and the result becomes a part of the string. We can put anything in there: a variable like undefinedundefinedname or an arithmetical expression like undefinedundefined1 + 2 or something more complex.undefinedundefined

    undefinedundefined

    Please note that this can only be done in backticks. Other quotes don't have this embedding functionality! undefinedundefinedjs run alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do nothing)undefinedundefined

    undefinedundefined

    We'll cover strings more thoroughly in the chapter undefinedundefinedinfo:string.undefinedundefined

    undefinedundefined

    smart header="There is no undefinedundefinedcharacter type." In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is called "char".undefinedundefined

    undefinedundefined

    In JavaScript, there is no such type. There's only one type: undefinedundefinedstring. A string may consist of zero characters (be empty), one character or many of them. undefinedundefined

    undefinedundefined

    Boolean (logical type)

    undefinedundefined

    The boolean type has only two values: undefinedundefinedtrue and undefinedundefinedfalse.undefinedundefined

    undefinedundefined

    This type is commonly used to store yes/no values: undefinedundefinedtrue means "yes, correct", and undefinedundefinedfalse means "no, incorrect".undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    Boolean values also come as a result of comparisons:

    undefinedundefined

    run let isGreater = 4 > 1;

    undefinedundefined

    alert( isGreater ); // true (the comparison result is "yes")

    undefinedundefined

    We'll cover booleans more deeply in the chapter undefinedundefinedinfo:logical-operators.undefinedundefined

    undefinedundefined

    The "null" value

    undefinedundefined

    The special undefinedundefinednull value does not belong to any of the types described above.undefinedundefined

    undefinedundefined

    It forms a separate type of its own which contains only the undefinedundefinednull value:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedlet age undefinedundefined=undefinedundefinednullundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    In JavaScript, undefinedundefinednull is not a "reference to a non-existing object" or a "null pointer" like in some other languages.undefinedundefined

    undefinedundefined

    It's just a special value which represents "nothing", "empty" or "value unknown".

    undefinedundefined

    The code above states that undefinedundefinedage is unknown.undefinedundefined

    undefinedundefined

    The "undefined" value

    undefinedundefined

    The special value undefinedundefinedundefined also stands apart. It makes a type of its own, just like undefinedundefinednull.undefinedundefined

    undefinedundefined

    The meaning of undefinedundefinedundefined is "value is not assigned".undefinedundefined

    undefinedundefined

    If a variable is declared, but not assigned, then its value is undefinedundefinedundefined:undefinedundefined

    undefinedundefined

    run let age;

    undefinedundefined

    alert(age); // shows "undefined"

    undefinedundefined

    Technically, it is possible to explicitly assign undefinedundefinedundefined to a variable:undefinedundefined

    undefinedundefined

    run let age = 100;

    undefinedundefined

    // change the value to undefined age = undefined;

    undefinedundefined

    alert(age); // "undefined"

    undefinedundefined

    …But we don't recommend doing that. Normally, one uses undefinedundefinednull to assign an "empty" or "unknown" value to a variable, while undefinedundefinedundefined is reserved as a default initial value for unassigned things.undefinedundefined

    undefinedundefined

    Objects and Symbols

    undefinedundefined

    The undefinedundefinedobject type is special.undefinedundefined

    undefinedundefined

    All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities.

    undefinedundefined

    Being that important, objects deserve a special treatment. We'll deal with them later in the chapter undefinedundefinedinfo:object, after we learn more about primitives.undefinedundefined

    undefinedundefined

    The undefinedundefinedsymbol type is used to create unique identifiers for objects. We have to mention it here for the sake of completeness, but also postpone the details till we know objects.undefinedundefined

    undefinedundefined

    The typeof operator [#type-typeof]

    undefinedundefined

    The undefinedundefinedtypeof operator returns the type of the argument. It's useful when we want to process values of different types differently or just want to do a quick check.undefinedundefined

    undefinedundefined

    It supports two forms of syntax:

    undefinedundefined
      undefinedundefined
    1. As an operator: undefinedundefinedtypeof x.undefinedundefined
    2. undefinedundefined
    3. As a function: undefinedundefinedtypeof(x).undefinedundefined
    4. undefinedundefined
    undefinedundefined

    In other words, it works with parentheses or without them. The result is the same.

    undefinedundefined

    The call to undefinedundefinedtypeof x returns a string with the type name:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefinedtypeofundefinedundefinedundefinedundefinedundefined// "undefined"undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedtypeofundefinedundefined0undefinedundefined// "number"undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedtypeof 10n undefinedundefined// "bigint"undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedtypeofundefinedundefinedtrueundefinedundefined// "boolean"undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedtypeofundefinedundefined"foo"undefinedundefined// "string"undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedtypeofundefinedundefinedSymbol(undefinedundefined"id") undefinedundefined// "symbol"undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefinedtypeof Math undefinedundefined// "object"  (1)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedtypeof null // "object"  undefinedundefined(undefinedundefined2undefinedundefined)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefinedtypeof alert undefinedundefined// "function"  (3)undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    The last three lines may need additional explanation:

    undefinedundefined
      undefinedundefined
    1. undefinedundefinedMath is a built-in object that provides mathematical operations. We will learn it in the chapter undefinedundefinedinfo:number. Here, it serves just as an example of an object.undefinedundefined
    2. undefinedundefined
    3. The result of undefinedundefinedtypeof null is undefinedundefined"object". That's an officially recognized error in undefinedundefinedtypeof behavior, coming from the early days of JavaScript and kept for compatibility. Definitely, undefinedundefinednull is not an object. It is a special value with a separate type of its own.undefinedundefined
    4. undefinedundefined
    5. The result of undefinedundefinedtypeof alert is undefinedundefined"function", because undefinedundefinedalert is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But undefinedundefinedtypeof treats them differently, returning undefinedundefined"function". That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice.undefinedundefined
    6. undefinedundefined
    undefinedundefined

    Summary

    undefinedundefined

    There are 8 basic data types in JavaScript.

    undefinedundefined
      undefinedundefined
    • undefinedundefinednumber for numbers of any kind: integer or floating-point, integers are limited by undefinedundefined±(2undefinedundefined53-1)undefinedundefined.undefinedundefined
    • undefinedundefined
    • undefinedundefinedbigint is for integer numbers of arbitrary length.undefinedundefined
    • undefinedundefined
    • undefinedundefinedstring for strings. A string may have zero or more characters, there's no separate single-character type.undefinedundefined
    • undefinedundefined
    • undefinedundefinedboolean for undefinedundefinedtrue/undefinedundefinedfalse.undefinedundefined
    • undefinedundefined
    • undefinedundefinednull for unknown values - a standalone type that has a single value undefinedundefinednull.undefinedundefined
    • undefinedundefined
    • undefinedundefinedundefined for unassigned values - a standalone type that has a single value undefinedundefinedundefined.undefinedundefined
    • undefinedundefined
    • undefinedundefinedobject for more complex data structures.undefinedundefined
    • undefinedundefined
    • undefinedundefinedsymbol for unique identifiers.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The undefinedundefinedtypeof operator allows us to see which type is stored in a variable.undefinedundefined

    undefinedundefined
      undefinedundefined
    • Two forms: undefinedundefinedtypeof x or undefinedundefinedtypeof(x).undefinedundefined
    • undefinedundefined
    • Returns a string with the name of the type, like undefinedundefined"string".undefinedundefined
    • undefinedundefined
    • For undefinedundefinednull returns undefinedundefined"object" - this is an error in the language, it's not actually an object.undefinedundefined
    • undefinedundefined
    undefinedundefined

    In the next chapters, we'll concentrate on primitive values and once we're familiar with them, we'll move on to objects.

    undefinedundefined

    libs: - lodash

    undefinedundefined
    undefinedundefined

    Currying

    undefinedundefined

    undefinedundefinedCurrying is an advanced technique of working with functions. It's used not only in JavaScript, but in other languages as well.undefinedundefined

    undefinedundefined

    Currying is a transformation of functions that translates a function from callable as undefinedundefinedf(a, b, c) into callable as undefinedundefinedf(a)(b)(c).undefinedundefined

    undefinedundefined

    Currying doesn't call a function. It just transforms it.

    undefinedundefined

    Let's see an example first, to better understand what we're talking about, and then practical applications.

    undefinedundefined

    We'll create a helper function undefinedundefinedcurry(f) that performs currying for a two-argument undefinedundefinedf. In other words, undefinedundefinedcurry(f) for two-argument undefinedundefinedf(a, b) translates it into a function that runs as undefinedundefinedf(a)(b):undefinedundefined

    undefinedundefined

    run undefinedundefined! function curry(f) { // curry(f) does the currying transform return function(a) { return function(b) { return f(a, b); }; }; } undefinedundefined/!undefinedundefined

    undefinedundefined

    // usage function sum(a, b) { return a + b; }

    undefinedundefined

    let curriedSum = curry(sum);

    undefinedundefined

    alert( curriedSum(1)(2) ); // 3

    undefinedundefined

    As you can see, the implementation is straightforward: it's just two wrappers.

    undefinedundefined
      undefinedundefined
    • The result of undefinedundefinedcurry(func) is a wrapper undefinedundefinedfunction(a).undefinedundefined
    • undefinedundefined
    • When it is called like undefinedundefinedcurriedSum(1), the argument is saved in the Lexical Environment, and a new wrapper is returned undefinedundefinedfunction(b).undefinedundefined
    • undefinedundefined
    • Then this wrapper is called with undefinedundefined2 as an argument, and it passes the call to the original undefinedundefinedsum.undefinedundefined
    • undefinedundefined
    undefinedundefined

    More advanced implementations of currying, such as undefinedundefined_.curry from lodash library, return a wrapper that allows a function to be called both normally and partially:undefinedundefined

    undefinedundefined

    run function sum(a, b) { return a + b; }

    undefinedundefined

    let curriedSum = undefinedundefined.curry(sum); // using .curry from lodash libraryundefinedundefined

    undefinedundefined

    alert( curriedSum(1, 2) ); // 3, still callable normally alert( curriedSum(1)(2) ); // 3, called partially

    undefinedundefined

    Currying? What for?

    undefinedundefined

    To understand the benefits we need a worthy real-life example.

    undefinedundefined

    For instance, we have the logging function undefinedundefinedlog(date, importance, message) that formats and outputs the information. In real projects such functions have many useful features like sending logs over the network, here we'll just use undefinedundefinedalert:undefinedundefined

    undefinedundefinedundefinedundefined

    Let's curry it!

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedlog undefinedundefined=undefinedundefined_.undefinedundefinedcurry(log)undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    After that undefinedundefinedlog works normally:undefinedundefined

    undefinedundefinedundefinedundefined

    …But also works in the curried form:

    undefinedundefinedundefinedundefined

    Now we can easily make a convenience function for current logs:

    undefinedundefinedundefinedundefined

    Now undefinedundefinedlogNow is undefinedundefinedlog with fixed first argument, in other words "partially applied function" or "partial" for short.undefinedundefined

    undefinedundefined

    We can go further and make a convenience function for current debug logs:

    undefinedundefinedundefinedundefined

    So: 1. We didn't lose anything after currying: undefinedundefinedlog is still callable normally. 2. We can easily generate partial functions such as for today's logs.undefinedundefined

    undefinedundefined

    Advanced curry implementation

    undefinedundefined

    In case you'd like to get in to the details, here's the "advanced" curry implementation for multi-argument functions that we could use above.

    undefinedundefined

    It's pretty short:

    undefinedundefinedundefinedundefined

    Usage examples:

    undefinedundefinedundefinedundefined

    The new undefinedundefinedcurry may look complicated, but it's actually easy to understand.undefinedundefined

    undefinedundefined

    The result of undefinedundefinedcurry(func) call is the wrapper undefinedundefinedcurried that looks like this:undefinedundefined

    undefinedundefinedundefinedundefined

    When we run it, there are two undefinedundefinedif execution branches:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. If passed undefinedundefinedargs count is the same or more than the original function has in its definition (undefinedundefinedfunc.length) , then just pass the call to it using undefinedundefinedfunc.apply.undefinedundefined
    2. undefinedundefined
    3. Otherwise, get a partial: we don't call undefinedundefinedfunc just yet. Instead, another wrapper is returned, that will re-apply undefinedundefinedcurried providing previous arguments together with the new ones.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result.

    undefinedundefined

    smart header="Fixed-length functions only" The currying requires the function to have a fixed number of arguments.

    undefinedundefined

    A function that uses rest parameters, such as undefinedundefinedf(...args), can't be curried this way. undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="A little more than currying" By definition, currying should convertsum(a, b, c)undefinedundefinedintosum(a)(b)(c)`.undefinedundefined

    undefinedundefined

    But most implementations of currying in JavaScript are advanced, as described: they also keep the function callable in the multi-argument variant.

    undefinedundefined

    Summary

    undefinedundefined

    undefinedundefinedCurrying is a transform that makes undefinedundefinedf(a,b,c) callable as undefinedundefinedf(a)(b)(c). JavaScript implementations usually both keep the function callable normally and return the partial if the arguments count is not enough.undefinedundefined

    undefinedundefined

    Currying allows us to easily get partials. As we've seen in the logging example, after currying the three argument universal function undefinedundefinedlog(date, importance, message) gives us partials when called with one argument (like undefinedundefinedlog(date)) or two arguments (like undefinedundefinedlog(date, importance)).undefinedundefined

    undefinedundefined

    Reference Type

    undefinedundefined

    warn header="In-depth language feature" This article covers an advanced topic, to understand certain edge-cases better.

    undefinedundefined

    It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood.

    undefinedundefined

    A dynamically evaluated method call can lose undefinedundefinedthis.undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    run let user = { name: "John", hi() { alert(this.name); }, bye() { alert("Bye"); } };

    undefinedundefined

    user.hi(); // works

    undefinedundefined

    // now let's call user.hi or user.bye depending on the name undefinedundefined! (user.name == "John" ? user.hi : user.bye)(); // Error! undefinedundefined/! undefinedundefined

    undefinedundefined

    On the last line there is a conditional operator that chooses either undefinedundefineduser.hi or undefinedundefineduser.bye. In this case the result is undefinedundefineduser.hi.undefinedundefined

    undefinedundefined

    Then the method is immediately called with parentheses undefinedundefined(). But it doesn't work correctly!undefinedundefined

    undefinedundefined

    As you can see, the call results in an error, because the value of undefinedundefined"this" inside the call becomes undefinedundefinedundefined.undefinedundefined

    undefinedundefined

    This works (object dot method):

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefineduser.undefinedundefinedhi()undefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    This doesn't (evaluated method):

    undefinedundefinedundefinedundefined

    Why? If we want to understand why it happens, let's get under the hood of how undefinedundefinedobj.method() call works.undefinedundefined

    undefinedundefined

    Reference type explained

    undefinedundefined

    Looking closely, we may notice two operations in undefinedundefinedobj.method() statement:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. First, the dot undefinedundefined'.' retrieves the property undefinedundefinedobj.method.undefinedundefined
    2. undefinedundefined
    3. Then parentheses undefinedundefined() execute it.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    So, how does the information about undefinedundefinedthis get passed from the first part to the second one?undefinedundefined

    undefinedundefined

    If we put these operations on separate lines, then undefinedundefinedthis will be lost for sure:undefinedundefined

    undefinedundefined

    run let user = { name: "John", hi() { alert(this.name); } }

    undefinedundefined

    undefinedundefined! // split getting and calling the method in two lines let hi = user.hi; hi(); // Error, because this is undefined undefinedundefined/! undefinedundefined

    undefinedundefined

    Here undefinedundefinedhi = user.hi puts the function into the variable, and then on the last line it is completely standalone, and so there's no undefinedundefinedthis.undefinedundefined

    undefinedundefined

    undefinedundefinedTo make undefinedundefineduser.hi() calls work, JavaScript uses a trick - the dot undefinedundefined'.' returns not a function, but a value of the special undefinedundefinedReference Type.undefinedundefinedundefinedundefined

    undefinedundefined

    The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language.

    undefinedundefined

    The value of Reference Type is a three-value combination undefinedundefined(base, name, strict), where:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedbase is the object.undefinedundefined
    • undefinedundefined
    • undefinedundefinedname is the property name.undefinedundefined
    • undefinedundefined
    • undefinedundefinedstrict is true if undefinedundefineduse strict is in effect.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The result of a property access undefinedundefineduser.hi is not a function, but a value of Reference Type. For undefinedundefineduser.hi in strict mode it is:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// Reference Type valueundefinedundefinedundefinedundefined(userundefinedundefined,undefinedundefined"hi"undefinedundefined,undefinedundefinedtrue)undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    When parentheses undefinedundefined() are called on the Reference Type, they receive the full information about the object and its method, and can set the right undefinedundefinedthis (undefinedundefined=user in this case).undefinedundefined

    undefinedundefined

    Reference type is a special "intermediary" internal type, with the purpose to pass information from dot undefinedundefined. to calling parentheses undefinedundefined().undefinedundefined

    undefinedundefined

    Any other operation like assignment undefinedundefinedhi = user.hi discards the reference type as a whole, takes the value of undefinedundefineduser.hi (a function) and passes it on. So any further operation "loses" undefinedundefinedthis.undefinedundefined

    undefinedundefined

    So, as the result, the value of undefinedundefinedthis is only passed the right way if the function is called directly using a dot undefinedundefinedobj.method() or square brackets undefinedundefinedobj['method']() syntax (they do the same here). There are various ways to solve this problem such as undefinedundefinedfunc.bind().undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Reference Type is an internal type of the language.

    undefinedundefined

    Reading a property, such as with dot undefinedundefined. in undefinedundefinedobj.method() returns not exactly the property value, but a special "reference type" value that stores both the property value and the object it was taken from.undefinedundefined

    undefinedundefined

    That's for the subsequent method call undefinedundefined() to get the object and set undefinedundefinedthis to it.undefinedundefined

    undefinedundefined

    For all other operations, the reference type automatically becomes the property value (a function in our case).

    undefinedundefined

    The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression.

    undefinedundefined

    BigInt

    undefinedundefined

    [recent caniuse="bigint"]

    undefinedundefined

    undefinedundefinedBigInt is a special numeric type that provides support for integers of arbitrary length.undefinedundefined

    undefinedundefined

    A bigint is created by appending undefinedundefinedn to the end of an integer literal or by calling the function undefinedundefinedBigInt that creates bigints from strings, numbers etc.undefinedundefined

    undefinedundefinedundefinedundefined

    Math operators

    undefinedundefined

    undefinedundefinedBigInt can mostly be used like a regular number, for example:undefinedundefined

    undefinedundefined

    run alert(1n + 2n); // 3

    undefinedundefined

    alert(5n / 2n); // 2

    undefinedundefined

    Please note: the division undefinedundefined5/2 returns the result rounded towards zero, without the decimal part. All operations on bigints return bigints.undefinedundefined

    undefinedundefined

    We can't mix bigints and regular numbers:

    undefinedundefined

    undefinedundefinedjs run alert(1n + 2); // Error: Cannot mix BigInt and other typesundefinedundefined

    undefinedundefined

    We should explicitly convert them if needed: using either undefinedundefinedBigInt() or undefinedundefinedNumber(), like this:undefinedundefined

    undefinedundefined

    run let bigint = 1n; let number = 2;

    undefinedundefined

    // number to bigint alert(bigint + BigInt(number)); // 3

    undefinedundefined

    // bigint to number alert(Number(bigint) + number); // 3

    undefinedundefined

    The conversion operations are always silent, never give errors, but if the bigint is too huge and won't fit the number type, then extra bits will be cut off, so we should be careful doing such conversion.

    undefinedundefined

    undefinedundefinedsmart header="The unary plus is not supported on bigints" The unary plus operator+valueundefinedundefinedis a well-known way to convertvalue` to a number.undefinedundefined

    undefinedundefined

    In order to avoid confusion, it's not supported on bigints: run let bigint = 1n;

    undefinedundefined

    alert( +bigint ); // error

    undefinedundefined
    undefinedundefinedSo we should use `Number()` to convert a bigint to a number.undefinedundefined
    undefinedundefined

    Comparisons

    undefinedundefined

    Comparisons, such as undefinedundefined<, undefinedundefined> work with bigints and numbers just fine:undefinedundefined

    undefinedundefined

    run alert( 2n > 1n ); // true

    undefinedundefined

    alert( 2n > 1 ); // true

    undefinedundefined

    Please note though, as numbers and bigints belong to different types, they can be equal undefinedundefined==, but not strictly equal undefinedundefined===:undefinedundefined

    undefinedundefined

    run alert( 1 == 1n ); // true

    undefinedundefined

    alert( 1 === 1n ); // false

    undefinedundefined

    Boolean operations

    undefinedundefined

    When inside undefinedundefinedif or other boolean operations, bigints behave like numbers.undefinedundefined

    undefinedundefined

    For instance, in undefinedundefinedif, bigint undefinedundefined0n is falsy, other values are truthy:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run if (0n) { // never executes }undefinedundefined

    undefinedundefined

    Boolean operators, such as undefinedundefined||, undefinedundefined&& and others also work with bigints similar to numbers:undefinedundefined

    undefinedundefined

    run alert( 1n || 2 ); // 1 (1n is considered truthy)

    undefinedundefined

    alert( 0n || 2 ); // 2 (0n is considered falsy)

    undefinedundefined

    Polyfills

    undefinedundefined

    Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as undefinedundefined+, undefinedundefined- and so on behave differently with bigints compared to regular numbers.undefinedundefined

    undefinedundefined

    For example, division of bigints always returns a bigint (rounded if necessary).

    undefinedundefined

    To emulate such behavior, a polyfill would need to analyze the code and replace all such operators with its functions. But doing so is cumbersome and would cost a lot of performance.

    undefinedundefined

    So, there's no well-known good polyfill.

    undefinedundefined

    Although, the other way around is proposed by the developers of undefinedundefinedJSBI library.undefinedundefined

    undefinedundefined

    This library implements big numbers using its own methods. We can use them instead of native bigints:

    undefinedundefined undefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    Operationnative undefinedundefinedBigIntundefinedundefined JSBI
    Creation from Number undefinedundefineda = BigInt(789)undefinedundefined undefinedundefineda = JSBI.BigInt(789)undefinedundefined
    Addition undefinedundefinedc = a + bundefinedundefined undefinedundefinedc = JSBI.add(a, b)undefinedundefined
    Subtraction undefinedundefinedc = a - bundefinedundefined undefinedundefinedc = JSBI.subtract(a, b)undefinedundefined
    undefinedundefined

    …And then use the polyfill (Babel plugin) to convert JSBI calls to native bigints for those browsers that support them.

    undefinedundefined

    In other words, this approach suggests that we write code in JSBI instead of native bigints. But JSBI works with numbers as with bigints internally, emulates them closely following the specification, so the code will be "bigint-ready".

    undefinedundefined

    We can use such JSBI code "as is" for engines that don't support bigints and for those that do support - the polyfill will convert the calls to native bigints.

    undefinedundefined

    References

    undefinedundefined
      undefinedundefined
    • undefinedundefinedMDN docs on BigInt.undefinedundefined
    • undefinedundefined
    • undefinedundefinedSpecification.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Browser environment, specs

    undefinedundefined

    The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms.

    undefinedundefined

    A platform may be a browser, or a web-server or another undefinedundefinedhost, even a "smart" coffee machine, if it can run JavaScript. Each of them provides platform-specific functionality. The JavaScript specification calls that a undefinedundefinedhost environment.undefinedundefined

    undefinedundefined

    A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.

    undefinedundefined

    Here's a bird's-eye view of what we have when JavaScript runs in a web browser:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    There's a "root" object called undefinedundefinedwindow. It has two roles:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. First, it is a global object for JavaScript code, as described in the chapter undefinedundefinedinfo:global-object.undefinedundefined
    2. undefinedundefined
    3. Second, it represents the "browser window" and provides methods to control it.
    4. undefinedundefined
    undefinedundefined

    For instance, here we use it as a global object:

    undefinedundefined

    run function sayHi() { alert("Hello"); }

    undefinedundefined

    // global functions are methods of the global object: window.sayHi();

    undefinedundefined

    And here we use it as a browser window, to see the window height:

    undefinedundefined

    undefinedundefinedjs run alert(window.innerHeight); // inner window heightundefinedundefined

    undefinedundefined

    There are more window-specific methods and properties, we'll cover them later.

    undefinedundefined

    DOM (Document Object Model)

    undefinedundefined

    Document Object Model, or DOM for short, represents all page content as objects that can be modified.

    undefinedundefined

    The undefinedundefineddocument object is the main "entry point" to the page. We can change or create anything on the page using it.undefinedundefined

    undefinedundefined

    For instance: run // change the background color to red document.body.style.background = "red";

    undefinedundefined

    // change it back after 1 second setTimeout(() => document.body.style.background = "", 1000);

    undefinedundefined

    Here we used undefinedundefineddocument.body.style, but there's much, much more. Properties and methods are described in the specification: undefinedundefinedDOM Living Standard.undefinedundefined

    undefinedundefined

    smart header="DOM is not only for browsers" The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too.

    undefinedundefined

    For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though.

    undefinedundefined

    smart header="CSSOM for styling" There's also a separate specification, undefinedundefinedCSS Object Model (CSSOM) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them.undefinedundefined

    undefinedundefined

    CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible.

    undefinedundefined

    BOM (Browser Object Model)

    undefinedundefined

    The Browser Object Model (BOM) represents additional objects provided by the browser (host environment) for working with everything except the document.

    undefinedundefined

    For instance:

    undefinedundefined
      undefinedundefined
    • The undefinedundefinednavigator object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: undefinedundefinednavigator.userAgent - about the current browser, and undefinedundefinednavigator.platform - about the platform (can help to differ between Windows/Linux/Mac etc).undefinedundefined
    • undefinedundefined
    • The undefinedundefinedlocation object allows us to read the current URL and can redirect the browser to a new one.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Here's how we can use the undefinedundefinedlocation object:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert(location.href); // shows current URL if (confirm("Go to Wikipedia?")) { location.href = "https://wikipedia.org"; // redirect the browser to another URL }undefinedundefined

    undefinedundefined

    Functions undefinedundefinedalert/confirm/prompt are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user.undefinedundefined

    undefinedundefined

    smart header="Specifications" BOM is the part of the general undefinedundefinedHTML specification.undefinedundefined

    undefinedundefined

    Yes, you heard that right. The HTML spec at undefinedundefinedhttps://html.spec.whatwg.org is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at undefinedundefinedhttps://spec.whatwg.org. undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    Talking about standards, we have:

    undefinedundefined
    undefinedundefined
    DOM specification
    undefinedundefined
    Describes the document structure, manipulations and events, see undefinedundefinedhttps://dom.spec.whatwg.org. undefinedundefined
    undefinedundefined
    CSSOM specification
    undefinedundefined
    Describes stylesheets and style rules, manipulations with them and their binding to documents, see undefinedundefinedhttps://www.w3.org/TR/cssom-1/. undefinedundefined
    undefinedundefined
    HTML specification
    undefinedundefined
    Describes the HTML language (e.g. tags) and also the BOM (browser object model) - various browser functions: undefinedundefinedsetTimeout, undefinedundefinedalert, undefinedundefinedlocation and so on, see undefinedundefinedhttps://html.spec.whatwg.org. It takes the DOM specification and extends it with many additional properties and methods. undefinedundefined
    undefinedundefined
    undefinedundefined

    Additionally, some classes are described separately at undefinedundefinedhttps://spec.whatwg.org/.undefinedundefined

    undefinedundefined

    Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything.

    undefinedundefined

    When you'd like to read about a property or a method, the Mozilla manual at undefinedundefinedhttps://developer.mozilla.org/en-US/search is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.undefinedundefined

    undefinedundefined

    To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g undefinedundefinedhttps://google.com?q=whatwg+localstorage, undefinedundefinedhttps://google.com?q=mdn+localstorage.undefinedundefined

    undefinedundefined

    Now we'll get down to learning DOM, because the document plays the central role in the UI.

    undefinedundefined

    libs: - d3 - domtree

    undefinedundefined
    undefinedundefined

    DOM tree

    undefinedundefined

    The backbone of an HTML document is tags.

    undefinedundefined

    According to the Document Object Model (DOM), every HTML tag is an object. Nested tags are "children" of the enclosing one. The text inside a tag is an object as well.

    undefinedundefined

    All these objects are accessible using JavaScript, and we can use them to modify the page.

    undefinedundefined

    For example, undefinedundefineddocument.body is the object representing the undefinedundefined<body> tag.undefinedundefined

    undefinedundefined

    Running this code will make the undefinedundefined<body> red for 3 seconds:undefinedundefined

    undefinedundefined

    run document.body.style.background = ‘red'; // make the background red

    undefinedundefined

    setTimeout(() => document.body.style.background = '', 3000); // return back

    undefinedundefined

    Here we used undefinedundefinedstyle.background to change the background color of undefinedundefineddocument.body, but there are many other properties, such as:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedinnerHTML - HTML contents of the node.undefinedundefined
    • undefinedundefined
    • undefinedundefinedoffsetWidth - the node width (in pixels)undefinedundefined
    • undefinedundefined
    • …and so on.
    • undefinedundefined
    undefinedundefined

    Soon we'll learn more ways to manipulate the DOM, but first we need to know about its structure.

    undefinedundefined

    An example of the DOM

    undefinedundefined

    Let's start with the following simple document:

    undefinedundefined

    undefinedundefinedhtml run no-beautify <!DOCTYPE HTML> <html> <head> <title>About elk</title> </head> <body> The truth about elk. </body> </html>undefinedundefined

    undefinedundefined

    The DOM represents HTML as a tree structure of tags. Here's how it looks:

    undefinedundefined
    undefinedundefined undefinedundefined
    undefinedundefinedOn the picture above, you can click on element nodes and their children will open/collapse.undefinedundefined
    undefinedundefined

    Every tree node is an object.

    undefinedundefined

    Tags are undefinedundefinedelement nodes (or just elements) and form the tree structure: undefinedundefined<html> is at the root, then undefinedundefined<head> and undefinedundefined<body> are its children, etc.undefinedundefined

    undefinedundefined

    The text inside elements forms undefinedundefinedtext nodes, labelled as undefinedundefined#text. A text node contains only a string. It may not have children and is always a leaf of the tree.undefinedundefined

    undefinedundefined

    For instance, the undefinedundefined<title> tag has the text undefinedundefined"About elk".undefinedundefined

    undefinedundefined

    Please note the special characters in text nodes:

    undefinedundefined
      undefinedundefined
    • a newline: undefinedundefined (in JavaScript known as undefinedundefined\n)undefinedundefined
    • undefinedundefined
    • a space: undefinedundefinedundefinedundefined
    • undefinedundefined
    undefinedundefined

    Spaces and newlines are totally valid characters, like letters and digits. They form text nodes and become a part of the DOM. So, for instance, in the example above the undefinedundefined<head> tag contains some spaces before undefinedundefined<title>, and that text becomes a undefinedundefined#text node (it contains a newline and some spaces only).undefinedundefined

    undefinedundefined

    There are only two top-level exclusions: 1. Spaces and newlines before undefinedundefined<head> are ignored for historical reasons. 2. If we put something after undefinedundefined</body>, then that is automatically moved inside the undefinedundefinedbody, at the end, as the HTML spec requires that all content must be inside undefinedundefined<body>. So there can't be any spaces after undefinedundefined</body>.undefinedundefined

    undefinedundefined

    In other cases everything's straightforward - if there are spaces (just like any character) in the document, then they become text nodes in the DOM, and if we remove them, then there won't be any.

    undefinedundefined

    Here are no space-only text nodes:

    undefinedundefined

    undefinedundefinedhtml no-beautify <!DOCTYPE HTML> <html><head><title>About elk</title></head><body>The truth about elk.</body></html>undefinedundefined

    undefinedundefined
    undefinedundefined undefinedundefined

    smart header="Spaces at string start/end and space-only text nodes are usually hidden in tools" Browser tools (to be covered soon) that work with DOM usually do not show spaces at the start/end of the text and empty text nodes (line-breaks) between tags.

    undefinedundefined

    Developer tools save screen space this way.

    undefinedundefined

    On further DOM pictures we'll sometimes omit them when they are irrelevant. Such spaces usually do not affect how the document is displayed.

    undefinedundefined

    Autocorrection

    undefinedundefined

    If the browser encounters malformed HTML, it automatically corrects it when making the DOM.

    undefinedundefined

    For instance, the top tag is always undefinedundefined<html>. Even if it doesn't exist in the document, it will exist in the DOM, because the browser will create it. The same goes for undefinedundefined<body>.undefinedundefined

    undefinedundefined

    As an example, if the HTML file is the single word undefinedundefined"Hello", the browser will wrap it into undefinedundefined<html> and undefinedundefined<body>, and add the required undefinedundefined<head>, and the DOM will be:undefinedundefined

    undefinedundefined
    undefinedundefined undefinedundefined

    While generating the DOM, browsers automatically process errors in the document, close tags and so on.

    undefinedundefined

    A document with unclosed tags:

    undefinedundefined

    undefinedundefinedhtml no-beautify <p>Hello <li>Mom <li>and <li>Dadundefinedundefined

    undefinedundefined

    …will become a normal DOM as the browser reads tags and restores the missing parts:

    undefinedundefined
    undefinedundefined undefinedundefinedwarn header="Tables always haveundefinedundefined undefinedundefined" An interesting "special case" is tables. By DOM specification they must haveundefinedundefined undefinedundefinedtag, but HTML text may omit it. Then the browser createsundefinedundefined undefinedundefined

    ` in the DOM automatically.

    undefinedundefined

    For the HTML:

    undefinedundefined

    undefinedundefinedhtml no-beautify <table id="table"><tr><td>1</td></tr></table>undefinedundefined

    DOM-structure will be: undefinedundefined
    undefinedundefined undefinedundefined

    You see? The undefinedundefined<tbody> appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. undefinedundefined

    undefinedundefined

    Other node types

    undefinedundefined

    There are some other node types besides elements and text nodes.

    undefinedundefined

    For example, comments:

    undefinedundefinedundefinedundefined
    undefinedundefined undefinedundefined

    We can see here a new tree node type - undefinedundefinedcomment node, labeled as undefinedundefined#comment, between two text nodes.undefinedundefined

    undefinedundefined

    We may think - why is a comment added to the DOM? It doesn't affect the visual representation in any way. But there's a rule - if something's in HTML, then it also must be in the DOM tree.

    undefinedundefined

    undefinedundefinedEverything in HTML, even comments, becomes a part of the DOM.undefinedundefined

    undefinedundefined

    Even the undefinedundefined<!DOCTYPE...> directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before undefinedundefined<html>. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there.undefinedundefined

    undefinedundefined

    The undefinedundefineddocument object that represents the whole document is, formally, a DOM node as well.undefinedundefined

    undefinedundefined

    There are undefinedundefined12 node types. In practice we usually work with 4 of them:undefinedundefined

    undefinedundefined
      undefinedundefined
    1. undefinedundefineddocument - the "entry point" into DOM.undefinedundefined
    2. undefinedundefined
    3. element nodes - HTML-tags, the tree building blocks.
    4. undefinedundefined
    5. text nodes - contain text.
    6. undefinedundefined
    7. comments - sometimes we can put information there, it won't be shown, but JS can read it from the DOM.
    8. undefinedundefined
    undefinedundefined

    See it for yourself

    undefinedundefined

    To see the DOM structure in real-time, try undefinedundefinedLive DOM Viewer. Just type in the document, and it will show up as a DOM at an instant.undefinedundefined

    undefinedundefined

    Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing.

    undefinedundefined

    To do so, open the web page undefinedundefinedelk.html, turn on the browser developer tools and switch to the Elements tab.undefinedundefined

    undefinedundefined

    It should look like this:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    You can see the DOM, click on elements, see their details and so on.

    undefinedundefined

    Please note that the DOM structure in developer tools is simplified. Text nodes are shown just as text. And there are no "blank" (space only) text nodes at all. That's fine, because most of the time we are interested in element nodes.

    undefinedundefined

    Clicking the undefinedundefined button in the left-upper corner allows us to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it.undefinedundefined

    undefinedundefined

    Another way to do it would be just right-clicking on a webpage and selecting "Inspect" in the context menu.

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    At the right part of the tools there are the following subtabs: - undefinedundefinedStyles - we can see CSS applied to the current element rule by rule, including built-in rules (gray). Almost everything can be edited in-place, including the dimensions/margins/paddings of the box below. - undefinedundefinedComputed - to see CSS applied to the element by property: for each property we can see a rule that gives it (including CSS inheritance and such). - undefinedundefinedEvent Listeners - to see event listeners attached to DOM elements (we'll cover them in the next part of the tutorial). - …and so on.undefinedundefined

    undefinedundefined

    The best way to study them is to click around. Most values are editable in-place.

    undefinedundefined

    Interaction with console

    undefinedundefined

    As we work the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see the result. Here are few tips to travel between the Elements tab and the console.

    undefinedundefined

    For the start:

    undefinedundefined
      undefinedundefined
    1. Select the first undefinedundefined<li> in the Elements tab.undefinedundefined
    2. undefinedundefined
    3. Press undefinedundefinedkey:Esc - it will open console right below the Elements tab.undefinedundefined
    4. undefinedundefined
    undefinedundefined

    Now the last selected element is available as undefinedundefined$0, the previously selected is undefinedundefined$1 etc.undefinedundefined

    undefinedundefined

    We can run commands on them. For instance, undefinedundefined$0.style.background = 'red' makes the selected list item red, like this:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    That's how to get a node from Elements in Console.

    undefinedundefined

    There's also a road back. If there's a variable referencing a DOM node, then we can use the command undefinedundefinedinspect(node) in Console to see it in the Elements pane.undefinedundefined

    undefinedundefined

    Or we can just output the DOM node in the console and explore "in-place", like undefinedundefineddocument.body below:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    That's for debugging purposes of course. From the next chapter on we'll access and modify DOM using JavaScript.

    undefinedundefined

    The browser developer tools are a great help in development: we can explore the DOM, try things and see what goes wrong.

    undefinedundefined

    Summary

    undefinedundefined

    An HTML/XML document is represented inside the browser as the DOM tree.

    undefinedundefined
      undefinedundefined
    • Tags become element nodes and form the structure.
    • undefinedundefined
    • Text becomes text nodes.
    • undefinedundefined
    • …etc, everything in HTML has its place in DOM, even comments.
    • undefinedundefined
    undefinedundefined

    We can use developer tools to inspect DOM and modify it manually.

    undefinedundefined

    Here we covered the basics, the most used and important actions to start with. There's an extensive documentation about Chrome Developer Tools at undefinedundefinedhttps://developers.google.com/web/tools/chrome-devtools. The best way to learn the tools is to click here and there, read menus: most options are obvious. Later, when you know them in general, read the docs and pick up the rest.undefinedundefined

    undefinedundefined

    DOM nodes have properties and methods that allow us to travel between them, modify them, move around the page, and more. We'll get down to them in the next chapters.

    undefinedundefined

    libs: - d3 - domtree

    undefinedundefined
    undefinedundefined

    Walking the DOM

    undefinedundefined

    The DOM allows us to do anything with elements and their contents, but first we need to reach the corresponding DOM object.

    undefinedundefined

    All operations on the DOM start with the undefinedundefineddocument object. That's the main "entry point" to DOM. From it we can access any node.undefinedundefined

    undefinedundefined

    Here's a picture of links that allow for travel between DOM nodes:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Let's discuss them in more detail.

    undefinedundefined

    On top: documentElement and body

    undefinedundefined

    The topmost tree nodes are available directly as undefinedundefineddocument properties:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefined<html> = undefinedundefineddocument.documentElementundefinedundefined
    undefinedundefined
    The topmost document node is undefinedundefineddocument.documentElement. That's the DOM node of the undefinedundefined<html> tag. undefinedundefined
    undefinedundefined
    undefinedundefined<body> = undefinedundefineddocument.bodyundefinedundefined
    undefinedundefined
    Another widely used DOM node is the undefinedundefined<body> element - undefinedundefineddocument.body. undefinedundefined
    undefinedundefined
    undefinedundefined<head> = undefinedundefineddocument.headundefinedundefined
    undefinedundefined
    The undefinedundefined<head> tag is available as undefinedundefineddocument.head. undefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefinedwarn header="There's a catch:document.bodyundefinedundefinedcan benull`" A script cannot access an element that doesn't exist at the moment of running.undefinedundefined

    undefinedundefined

    In particular, if a script is inside undefinedundefined<head>, then undefinedundefineddocument.body is unavailable, because the browser did not read it yet.undefinedundefined

    undefinedundefined

    So, in the example below the first undefinedundefinedalert shows undefinedundefinednull:undefinedundefined

    run undefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined undefinedundefinedundefinedundefined undefinedundefined
    undefinedundefinedundefinedundefined
    undefinedundefined

    undefinedundefinedsmart header="In the DOM world `null` means \"doesn't exist\"" In the DOM, the `null` value means "doesn't exist" or "no such node".undefinedundefined

    undefinedundefined

    Children: childNodes, firstChild, lastChild

    undefinedundefined

    There are two terms that we'll use from now on:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedChild nodes (or children) - elements that are direct children. In other words, they are nested exactly in the given one. For instance, undefinedundefined<head> and undefinedundefined<body> are children of undefinedundefined<html> element.undefinedundefined
    • undefinedundefined
    • undefinedundefinedDescendants - all elements that are nested in the given one, including children, their children and so on.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance, here undefinedundefined<body> has children undefinedundefined<div> and undefinedundefined<ul> (and few blank text nodes):undefinedundefined

    run undefinedundefinedundefinedundefined undefinedundefined
    Begin
    undefinedundefined
      undefinedundefined
    • undefinedundefinedInformationundefinedundefined
    • undefinedundefined
    undefinedundefinedundefinedundefined undefinedundefined

    undefinedundefined

    …And descendants of undefinedundefined<body> are not only direct children undefinedundefined<div>, undefinedundefined<ul> but also more deeply nested elements, such as undefinedundefined<li> (a child of undefinedundefined<ul>) and undefinedundefined<b> (a child of undefinedundefined<li>) - the entire subtree.undefinedundefined

    undefinedundefined

    undefinedundefinedThe undefinedundefinedchildNodes collection lists all child nodes, including text nodes.undefinedundefinedundefinedundefined

    undefinedundefined

    The example below shows children of undefinedundefineddocument.body:undefinedundefined

    run undefinedundefinedundefinedundefined undefinedundefined
    Begin
    undefinedundefined
      undefinedundefined
    • Information
    • undefinedundefined
    undefinedundefined
    End
    undefinedundefined …more stuff… undefinedundefinedundefinedundefined undefinedundefined

    undefinedundefined

    Please note an interesting detail here. If we run the example above, the last element shown is undefinedundefined<script>. In fact, the document has more stuff below, but at the moment of the script execution the browser did not read it yet, so the script doesn't see it.undefinedundefined

    undefinedundefined

    undefinedundefinedProperties undefinedundefinedfirstChild and undefinedundefinedlastChild give fast access to the first and last children.undefinedundefinedundefinedundefined

    undefinedundefined

    They are just shorthands. If there exist child nodes, then the following is always true:

    undefinedundefinedundefinedundefined

    There's also a special function undefinedundefinedelem.hasChildNodes() to check whether there are any child nodes.undefinedundefined

    undefinedundefined

    DOM collections

    undefinedundefined

    As we can see, undefinedundefinedchildNodes looks like an array. But actually it's not an array, but rather a undefinedundefinedcollection - a special array-like iterable object.undefinedundefined

    undefinedundefined

    There are two important consequences:

    undefinedundefined
      undefinedundefined
    1. We can use undefinedundefinedfor..of to iterate over it:undefinedundefined
    2. undefinedundefined
    undefinedundefinedundefinedundefined

    That's because it's iterable (provides the undefinedundefinedSymbol.iterator property, as required).undefinedundefined

    undefinedundefined
      undefinedundefined
    1. Array methods won't work, because it's not an array: undefinedundefinedjs run alert(document.body.childNodes.filter); // undefined (there's no filter method!)undefinedundefined
    2. undefinedundefined
    undefinedundefined

    The first thing is nice. The second is tolerable, because we can use undefinedundefinedArray.from to create a "real" array from the collection, if we want array methods:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( Array.from(document.body.childNodes).filter ); // functionundefinedundefined

    undefinedundefined

    warn header="DOM collections are read-only" DOM collections, and even more - undefinedundefinedall navigation properties listed in this chapter are read-only.undefinedundefined

    undefinedundefined

    We can't replace a child by something else by assigning undefinedundefinedchildNodes[i] = ....undefinedundefined

    undefinedundefined

    Changing DOM needs other methods. We will see them in the next chapter.

    undefinedundefined

    warn header="DOM collections are live" Almost all DOM collections with minor exceptions are undefinedundefinedlive. In other words, they reflect the current state of DOM.undefinedundefined

    undefinedundefined

    If we keep a reference to undefinedundefinedelem.childNodes, and add/remove nodes into DOM, then they appear in the collection automatically. undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Don't usefor..inundefinedundefinedto loop over collections" Collections are iterable usingfor..ofundefinedundefined. Sometimes people try to usefor..in` for that.undefinedundefined

    undefinedundefined

    Please, don't. The undefinedundefinedfor..in loop iterates over all enumerable properties. And collections have some "extra" rarely used properties that we usually do not want to get:undefinedundefined

    run undefinedundefined undefinedundefined undefinedundefinedundefinedundefined

    undefinedundefined

    Siblings and the parent

    undefinedundefined

    undefinedundefinedSiblings are nodes that are children of the same parent.undefinedundefined

    undefinedundefined

    For instance, here undefinedundefined<head> and undefinedundefined<body> are siblings:undefinedundefined

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined<html>undefinedundefinedundefinedundefinedundefinedundefined<head>...undefinedundefined</head><body>...undefinedundefined</body>undefinedundefinedundefinedundefinedundefinedundefined</html>undefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined
      undefinedundefined
    • undefinedundefined<body> is said to be the "next" or "right" sibling of undefinedundefined<head>,undefinedundefined
    • undefinedundefined
    • undefinedundefined<head> is said to be the "previous" or "left" sibling of undefinedundefined<body>.undefinedundefined
    • undefinedundefined
    undefinedundefined

    The next sibling is in undefinedundefinednextSibling property, and the previous one - in undefinedundefinedpreviousSibling.undefinedundefined

    undefinedundefined

    The parent is available as undefinedundefinedparentNode.undefinedundefined

    undefinedundefined

    For example:

    run // parent of undefinedundefined is undefinedundefinedundefinedundefined

    alert( document.body.parentNode === document.documentElement ); // true

    // after undefinedundefined goes undefinedundefined undefinedundefined

    alert( document.head.nextSibling ); // HTMLBodyElement

    // before undefinedundefined goes undefinedundefined undefinedundefined

    alert( document.body.previousSibling ); // HTMLHeadElement

    undefinedundefined

    Element-only navigation

    undefinedundefined

    Navigation properties listed above refer to undefinedundefinedall nodes. For instance, in undefinedundefinedchildNodes we can see both text nodes, element nodes, and even comment nodes if they exist.undefinedundefined

    undefinedundefined

    But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page.

    undefinedundefined

    So let's see more navigation links that only take undefinedundefinedelement nodes into account:undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The links are similar to those given above, just with undefinedundefinedElement word inside:undefinedundefined

    undefinedundefined
      undefinedundefined
    • undefinedundefinedchildren - only those children that are element nodes.undefinedundefined
    • undefinedundefined
    • undefinedundefinedfirstElementChild, undefinedundefinedlastElementChild - first and last element children.undefinedundefined
    • undefinedundefined
    • undefinedundefinedpreviousElementSibling, undefinedundefinednextElementSibling - neighbor elements.undefinedundefined
    • undefinedundefined
    • undefinedundefinedparentElement - parent element.undefinedundefined
    • undefinedundefined
    undefinedundefined

    undefinedundefinedsmart header="WhyparentElementundefinedundefined? Can the parent be *not* an element?" TheparentElementundefinedundefinedproperty returns the "element" parent, whileparentNode` returns "any node" parent. These properties are usually the same: they both get the parent.undefinedundefined

    undefinedundefined

    With the one exception of undefinedundefineddocument.documentElement:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( document.documentElement.parentNode ); // document alert( document.documentElement.parentElement ); // nullundefinedundefined

    undefinedundefined

    The reason is that the root node undefinedundefineddocument.documentElement (undefinedundefined<html>) has undefinedundefineddocument as its parent. But undefinedundefineddocument is not an element node, so undefinedundefinedparentNode returns it and undefinedundefinedparentElement does not.undefinedundefined

    undefinedundefined

    This detail may be useful when we want to travel up from an arbitrary element undefinedundefinedelem to undefinedundefined<html>, but not to the undefinedundefineddocument:undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    Let's modify one of the examples above: replace undefinedundefinedchildNodes with undefinedundefinedchildren. Now it shows only elements:undefinedundefined

    run undefinedundefinedundefinedundefined undefinedundefined
    Begin
    undefinedundefined
      undefinedundefined
    • Information
    • undefinedundefined
    undefinedundefined
    End
    undefinedundefined … undefinedundefinedundefinedundefined undefinedundefined

    undefinedundefinedundefinedundefined

    Till now we described the basic navigation properties.

    undefinedundefined

    Certain types of DOM elements may provide additional properties, specific to their type, for convenience.

    undefinedundefined

    Tables are a great example of that, and represent a particularly important case:

    undefinedundefined

    undefinedundefinedThe undefinedundefined<table>undefinedundefined element supports (in addition to the given above) these properties: - undefinedundefinedtable.rows - the collection of undefinedundefined<tr> elements of the table. - undefinedundefinedtable.caption/tHead/tFoot - references to elements undefinedundefined<caption>, undefinedundefined<thead>, undefinedundefined<tfoot>. - undefinedundefinedtable.tBodies - the collection of undefinedundefined<tbody> elements (can be many according to the standard, but there will always be at least one - even if it is not in the source HTML, the browser will put it in the DOM).undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined<thead>, undefinedundefined<tfoot>, undefinedundefined<tbody>undefinedundefined elements provide the undefinedundefinedrows property: - undefinedundefinedtbody.rows - the collection of undefinedundefined<tr> inside.undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined<tr>:undefinedundefined - undefinedundefinedtr.cells - the collection of undefinedundefined<td> and undefinedundefined<th> cells inside the given undefinedundefined<tr>. - undefinedundefinedtr.sectionRowIndex - the position (index) of the given undefinedundefined<tr> inside the enclosing undefinedundefined<thead>/<tbody>/<tfoot>. - undefinedundefinedtr.rowIndex - the number of the undefinedundefined<tr> in the table as a whole (including all table rows).undefinedundefined

    undefinedundefined

    undefinedundefinedundefinedundefined<td> and undefinedundefined<th>:undefinedundefined - undefinedundefinedtd.cellIndex - the number of the cell inside the enclosing undefinedundefined<tr>.undefinedundefined

    undefinedundefined

    An example of usage:

    run height=100 undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    one two
    three four
    undefinedundefined undefinedundefined

    undefinedundefined

    The specification: undefinedundefinedtabular data.undefinedundefined

    undefinedundefined

    There are also additional navigation properties for HTML forms. We'll look at them later when we start working with forms.

    undefinedundefined

    Summary

    undefinedundefined

    Given a DOM node, we can go to its immediate neighbors using navigation properties.

    undefinedundefined

    There are two main sets of them:

    undefinedundefined
      undefinedundefined
    • For all nodes: undefinedundefinedparentNode, undefinedundefinedchildNodes, undefinedundefinedfirstChild, undefinedundefinedlastChild, undefinedundefinedpreviousSibling, undefinedundefinednextSibling.undefinedundefined
    • undefinedundefined
    • For element nodes only: undefinedundefinedparentElement, undefinedundefinedchildren, undefinedundefinedfirstElementChild, undefinedundefinedlastElementChild, undefinedundefinedpreviousElementSibling, undefinedundefinednextElementSibling.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Some types of DOM elements, e.g. tables, provide additional properties and collections to access their content.

    undefinedundefined

    Searching: getElementundefinedundefined, querySelectorundefinedundefined

    undefinedundefined

    DOM navigation properties are great when elements are close to each other. What if they are not? How to get an arbitrary element of the page?

    undefinedundefined

    There are additional searching methods for that.

    undefinedundefined

    document.getElementById or just id

    undefinedundefined

    If an element has the undefinedundefinedid attribute, we can get the element using the method undefinedundefineddocument.getElementById(id), no matter where it is.undefinedundefined

    undefinedundefined

    For instance:

    run undefinedundefined
    undefinedundefined
    Element
    undefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    Also, there's a global variable named by undefinedundefinedid that references the element:undefinedundefined

    run undefinedundefined
    undefinedundefined
    Element
    undefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    …That's unless we declare a JavaScript variable with the same name, then it takes precedence:

    run untrusted height=0 undefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    warn header="Please don't use id-named global variables to access elements" This behavior is described undefinedundefinedin the specification, so it's kind of standard. But it is supported mainly for compatibility.undefinedundefined

    undefinedundefined

    The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from.

    undefinedundefined

    Here in the tutorial we use undefinedundefinedid to directly reference an element for brevity, when it's obvious where the element comes from.undefinedundefined

    undefinedundefined

    In real life undefinedundefineddocument.getElementById is the preferred method. undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="Theidundefinedundefinedmust be unique" Theidundefinedundefinedmust be unique. There can be only one element in the document with the givenid`.undefinedundefined

    undefinedundefined

    If there are multiple elements with the same undefinedundefinedid, then the behavior of methods that use it is unpredictable, e.g. undefinedundefineddocument.getElementById may return any of such elements at random. So please stick to the rule and keep undefinedundefinedid unique. undefinedundefined

    undefinedundefined

    undefinedundefinedwarn header="Only `document.getElementById`, not `anyElem.getElementById`" The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document.undefinedundefined

    undefinedundefined

    querySelectorAll [#querySelectorAll]

    undefinedundefined

    By far, the most versatile method, undefinedundefinedelem.querySelectorAll(css) returns all elements inside undefinedundefinedelem matching the given CSS selector.undefinedundefined

    undefinedundefined

    Here we look for all undefinedundefined<li> elements that are last children:undefinedundefined

    run undefinedundefined
      undefinedundefined
    • The
    • undefinedundefined
    • test
    • undefinedundefined
    undefinedundefined
      undefinedundefined
    • has
    • undefinedundefined
    • passed
    • undefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    This method is indeed powerful, because any CSS selector can be used.

    undefinedundefined

    undefinedundefinedsmart header="Can use pseudo-classes as well" Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `<html>` to the most nested one).undefinedundefined

    undefinedundefined

    querySelector [#querySelector]

    undefinedundefined

    The call to undefinedundefinedelem.querySelector(css) returns the first element for the given CSS selector.undefinedundefined

    undefinedundefined

    In other words, the result is the same as undefinedundefinedelem.querySelectorAll(css)[0], but the latter is looking for undefinedundefinedall elements and picking one, while undefinedundefinedelem.querySelector just looks for one. So it's faster and also shorter to write.undefinedundefined

    undefinedundefined

    matches

    undefinedundefined

    Previous methods were searching the DOM.

    undefinedundefined

    The undefinedundefinedelem.matches(css) does not look for anything, it merely checks if undefinedundefinedelem matches the given CSS-selector. It returns undefinedundefinedtrue or undefinedundefinedfalse.undefinedundefined

    undefinedundefined

    The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us.

    undefinedundefined

    For instance:

    undefinedundefined

    run undefinedundefinedundefinedundefinedundefinedundefined

    undefinedundefined undefinedundefined

    undefinedundefined

    closest

    undefinedundefined

    undefinedundefinedAncestors of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top.undefinedundefined

    undefinedundefined

    The method undefinedundefinedelem.closest(css) looks for the nearest ancestor that matches the CSS-selector. The undefinedundefinedelem itself is also included in the search.undefinedundefined

    undefinedundefined

    In other words, the method undefinedundefinedclosest goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned.undefinedundefined

    undefinedundefined

    For instance:

    run undefinedundefined

    Contents

    undefinedundefined
    undefinedundefined
      undefinedundefined
    • Chapter 1
    • undefinedundefined
    • Chapter 1
    • undefinedundefined
    undefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    getElementsBy*

    undefinedundefined

    There are also other methods to look for nodes by a tag, class, etc.

    undefinedundefined

    Today, they are mostly history, as undefinedundefinedquerySelector is more powerful and shorter to write.undefinedundefined

    undefinedundefined

    So here we cover them mainly for completeness, while you can still find them in the old scripts.

    undefinedundefined
      undefinedundefined
    • undefinedundefinedelem.getElementsByTagName(tag) looks for elements with the given tag and returns the collection of them. The undefinedundefinedtag parameter can also be a star undefinedundefined"*" for "any tags".undefinedundefined
    • undefinedundefined
    • undefinedundefinedelem.getElementsByClassName(className) returns elements that have the given CSS class.undefinedundefined
    • undefinedundefined
    • undefinedundefineddocument.getElementsByName(name) returns elements with the given undefinedundefinedname attribute, document-wide. Very rarely used.undefinedundefined
    • undefinedundefined
    undefinedundefined

    For instance:

    undefinedundefinedundefinedundefined

    Let's find all undefinedundefinedinput tags inside the table:undefinedundefined

    run height=50 undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined
    Your age: undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    ``undefinedundefinedwarn header="Don't forget the"s"undefinedundefinedletter!" Novice developers sometimes forget the letter"s"undefinedundefined. That is, they try to callgetElementByTagName` instead of undefinedundefinedgetElementundefinedundefinedsByTagNameundefinedundefined.undefinedundefined

    undefinedundefined

    The undefinedundefined"s" letter is absent in undefinedundefinedgetElementById, because it returns a single element. But undefinedundefinedgetElementsByTagName returns a collection of elements, so there's undefinedundefined"s" inside. undefinedundefined

    undefinedundefined

    warn header="It returns a collection, not an element!" Another widespread novice mistake is to write:

    undefinedundefinedundefinedundefined

    That won't work, because it takes a undefinedundefinedcollection of inputs and assigns the value to it rather than to elements inside it.undefinedundefined

    undefinedundefined

    We should either iterate over the collection or get an element by its index, and then assign, like this:

    undefinedundefinedundefinedundefined

    undefinedundefined

    Looking for undefinedundefined.article elements:undefinedundefined

    run height=50 undefinedundefined
    undefinedundefined
    Article
    undefinedundefined
    Long article
    undefinedundefined
    undefinedundefined undefinedundefined

    undefinedundefined

    Live collections

    undefinedundefined

    All methods undefinedundefined"getElementsBy*" return a undefinedundefinedlive collection. Such collections always reflect the current state of the document and "auto-update" when it changes.undefinedundefined

    undefinedundefined

    In the example below, there are two scripts.

    undefinedundefined
      undefinedundefined
    1. The first one creates a reference to the collection of undefinedundefined<div>. As of now, its length is undefinedundefined1.undefinedundefined
    2. undefinedundefined
    3. The second scripts runs after the browser meets one more undefinedundefined<div>, so its length is undefinedundefined2.undefinedundefined
    4. undefinedundefined
    run undefinedundefined
    First div
    undefinedundefined undefinedundefined
    Second div
    undefinedundefined undefinedundefined

    undefinedundefined

    In contrast, undefinedundefinedquerySelectorAll returns a undefinedundefinedstatic collection. It's like a fixed array of elements.undefinedundefined

    undefinedundefined

    If we use it instead, then both scripts output undefinedundefined1:undefinedundefined

    run undefinedundefined
    First div
    undefinedundefined undefinedundefined
    Second div
    undefinedundefined undefinedundefined

    undefinedundefined

    Now we can easily see the difference. The static collection did not increase after the appearance of a new undefinedundefineddiv in the document.undefinedundefined

    undefinedundefined

    Summary

    undefinedundefined

    There are 6 main methods to search for nodes in DOM:

    undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined undefinedundefinedundefinedundefined undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined
    Method Searches by… Can call on an element? Live?
    undefinedundefinedquerySelectorundefinedundefined CSS-selector -
    undefinedundefinedquerySelectorAllundefinedundefined CSS-selector -
    undefinedundefinedgetElementByIdundefinedundefined undefinedundefinedidundefinedundefined - -
    undefinedundefinedgetElementsByNameundefinedundefined undefinedundefinednameundefinedundefined -
    undefinedundefinedgetElementsByTagNameundefinedundefined tag or undefinedundefined'''*'''undefinedundefined
    undefinedundefinedgetElementsByClassNameundefinedundefined class
    undefinedundefined

    By far the most used are undefinedundefinedquerySelector and undefinedundefinedquerySelectorAll, but undefinedundefinedgetElement(s)By* can be sporadically helpful or found in the old scripts.undefinedundefined

    undefinedundefined

    Besides that:

    undefinedundefined
      undefinedundefined
    • There is undefinedundefinedelem.matches(css) to check if undefinedundefinedelem matches the given CSS selector.undefinedundefined
    • undefinedundefined
    • There is undefinedundefinedelem.closest(css) to look for the nearest ancestor that matches the given CSS-selector. The undefinedundefinedelem itself is also checked.undefinedundefined
    • undefinedundefined
    undefinedundefined

    And let's mention one more method here to check for the child-parent relationship, as it's sometimes useful: - undefinedundefinedelemA.contains(elemB) returns true if undefinedundefinedelemB is inside undefinedundefinedelemA (a descendant of undefinedundefinedelemA) or when undefinedundefinedelemA==elemB.undefinedundefined

    undefinedundefined

    Node properties: type, tag and contents

    undefinedundefined

    Let's get a more in-depth look at DOM nodes.

    undefinedundefined

    In this chapter we'll see more into what they are and learn their most used properties.

    undefinedundefined

    DOM node classes

    undefinedundefined

    Different DOM nodes may have different properties. For instance, an element node corresponding to tag undefinedundefined<a> has link-related properties, and the one corresponding to undefinedundefined<input> has input-related properties and so on. Text nodes are not the same as element nodes. But there are also common properties and methods between all of them, because all classes of DOM nodes form a single hierarchy.undefinedundefined

    undefinedundefined

    Each DOM node belongs to the corresponding built-in class.

    undefinedundefined

    The root of the hierarchy is undefinedundefinedEventTarget, that is inherited by undefinedundefinedNode, and other DOM nodes inherit from it.undefinedundefined

    undefinedundefined

    Here's the picture, explanations to follow:

    undefinedundefined

    undefinedundefinedundefinedundefined

    undefinedundefined

    The classes are:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedEventTarget - is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later.undefinedundefined
    • undefinedundefined
    • undefinedundefinedNode - is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: undefinedundefinedparentNode, undefinedundefinednextSibling, undefinedundefinedchildNodes and so on (they are getters). Objects of undefinedundefinedNode class are never created. But there are concrete node classes that inherit from it, namely: undefinedundefinedText for text nodes, undefinedundefinedElement for element nodes and more exotic ones like undefinedundefinedComment for comment nodes.undefinedundefined
    • undefinedundefined
    • undefinedundefinedElement - is a base class for DOM elements. It provides element-level navigation like undefinedundefinednextElementSibling, undefinedundefinedchildren and searching methods like undefinedundefinedgetElementsByTagName, undefinedundefinedquerySelector. A browser supports not only HTML, but also XML and SVG. The undefinedundefinedElement class serves as a base for more specific classes: undefinedundefinedSVGElement, undefinedundefinedXMLElement and undefinedundefinedHTMLElement.undefinedundefined
    • undefinedundefined
    • undefinedundefinedHTMLElement - is finally the basic class for all HTML elements. It is inherited by concrete HTML elements: undefinedundefined
        undefinedundefined
      • undefinedundefinedHTMLInputElement - the class for undefinedundefined<input> elements,undefinedundefined
      • undefinedundefined
      • undefinedundefinedHTMLBodyElement - the class for undefinedundefined<body> elements,undefinedundefined
      • undefinedundefined
      • undefinedundefinedHTMLAnchorElement - the class for undefinedundefined<a> elements,undefinedundefined
      • undefinedundefined
      • …and so on, each tag has its own class that may provide specific properties and methods.
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    undefinedundefined

    So, the full set of properties and methods of a given node comes as the result of the inheritance.

    undefinedundefined

    For example, let's consider the DOM object for an undefinedundefined<input> element. It belongs to undefinedundefinedHTMLInputElement class.undefinedundefined

    undefinedundefined

    It gets properties and methods as a superposition of (listed in inheritance order):

    undefinedundefined
      undefinedundefined
    • undefinedundefinedHTMLInputElement - this class provides input-specific properties,undefinedundefined
    • undefinedundefined
    • undefinedundefinedHTMLElement - it provides common HTML element methods (and getters/setters),undefinedundefined
    • undefinedundefined
    • undefinedundefinedElement - provides generic element methods,undefinedundefined
    • undefinedundefined
    • undefinedundefinedNode - provides common DOM node properties,undefinedundefined
    • undefinedundefined
    • undefinedundefinedEventTarget - gives the support for events (to be covered),undefinedundefined
    • undefinedundefined
    • …and finally it inherits from undefinedundefinedObject, so "plain object" methods like undefinedundefinedhasOwnProperty are also available.undefinedundefined
    • undefinedundefined
    undefinedundefined

    To see the DOM node class name, we can recall that an object usually has the undefinedundefinedconstructor property. It references the class constructor, and undefinedundefinedconstructor.name is its name:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( document.body.constructor.name ); // HTMLBodyElementundefinedundefined

    undefinedundefined

    …Or we can just undefinedundefinedtoString it:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( document.body ); // [object HTMLBodyElement]undefinedundefined

    undefinedundefined

    We also can use undefinedundefinedinstanceof to check the inheritance:undefinedundefined

    undefinedundefined

    undefinedundefinedjs run alert( document.body instanceof HTMLBodyElement ); // true alert( document.body instanceof HTMLElement ); // true alert( document.body instanceof Element ); // true alert( document.body instanceof Node ); // true alert( document.body instanceof EventTarget ); // trueundefinedundefined

    undefinedundefined

    As we can see, DOM nodes are regular JavaScript objects. They use prototype-based classes for inheritance.

    undefinedundefined

    That's also easy to see by outputting an element with undefinedundefinedconsole.dir(elem) in a browser. There in the console you can see undefinedundefinedHTMLElement.prototype, undefinedundefinedElement.prototype and so on.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="console.dir(elem)undefinedundefinedversusconsole.log(elem)undefinedundefined" Most browsers support two commands in their developer tools:console.logundefinedundefinedandconsole.dir`. They output their arguments to the console. For JavaScript objects these commands usually do the same.undefinedundefined

    undefinedundefined

    But for DOM elements they are different:

    undefinedundefined
      undefinedundefined
    • undefinedundefinedconsole.log(elem) shows the element DOM tree.undefinedundefined
    • undefinedundefined
    • undefinedundefinedconsole.dir(elem) shows the element as a DOM object, good to explore its properties.undefinedundefined
    • undefinedundefined
    undefinedundefined

    Try it on undefinedundefineddocument.body. undefinedundefined

    undefinedundefined

    smart header="IDL in the spec" In the specification, DOM classes aren't described by using JavaScript, but a special undefinedundefinedInterface description language (IDL), that is usually easy to understand.undefinedundefined

    undefinedundefined

    In IDL all properties are prepended with their types. For instance, undefinedundefinedDOMString, undefinedundefinedboolean and so on.undefinedundefined

    undefinedundefined

    Here's an excerpt from it, with comments:

    undefinedundefined
    undefinedundefined
    undefinedundefinedundefinedundefinedundefinedundefined// Define HTMLInputElementundefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefined// The colon ":" means that HTMLInputElement inherits from HTMLElementundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefinedinterface HTMLInputElement: HTMLElement {undefinedundefinedundefinedundefinedundefinedundefined  // here go properties and methods of <input> elementsundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined  // "DOMString" means that the value of a property is a stringundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefined  attribute DOMString acceptundefinedundefined;undefinedundefinedundefinedundefined  attribute DOMString altundefinedundefined;undefinedundefinedundefinedundefined  attribute DOMString autocompleteundefinedundefined;undefinedundefinedundefinedundefined  attribute DOMString valueundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined*!*undefinedundefinedundefinedundefinedundefinedundefined// boolean value property (true/false)undefinedundefinedundefinedundefined  attribute boolean autofocusundefinedundefined;undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined  ...undefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined!undefinedundefined*undefinedundefinedundefinedundefinedundefinedundefined  // now the method: "void" means that the method returns no valueundefinedundefinedundefinedundefinedundefinedundefined*undefinedundefined/undefinedundefined!*undefinedundefinedundefinedundefinedundefinedundefinedvoidundefinedundefinedselect()undefinedundefined;undefinedundefinedundefinedundefined  ...undefinedundefined}undefinedundefinedundefinedundefined
    undefinedundefined
    undefinedundefined

    undefinedundefined

    The "nodeType" property

    undefinedundefined

    The undefinedundefinednodeType property provides one more, "old-fashioned" way to get the "type" of a DOM node.undefinedundefined

    undefinedundefined

    It has a numeric value: - undefinedundefinedelem.nodeType == 1 for element nodes, - undefinedundefinedelem.nodeType == 3 for text nodes, - undefinedundefinedelem.nodeType == 9 for the document object, - there are few other values in undefinedundefinedthe specification.undefinedundefined

    undefinedundefined

    For instance:

    run undefinedundefined undefinedundefined undefinedundefinedundefinedundefined

    undefinedundefined

    In modern scripts, we can use undefinedundefinedinstanceof and other class-based tests to see the node type, but sometimes undefinedundefinednodeType may be simpler. We can only read undefinedundefinednodeType, not change it.undefinedundefined

    undefinedundefined

    Tag: nodeName and tagName

    undefinedundefined

    Given a DOM node, we can read its tag name from undefinedundefinednodeName or undefinedundefinedtagName properties:undefinedundefined

    undefinedundefined

    For instance:

    undefinedundefined

    undefinedundefinedjs run alert( document.body.nodeName ); // BODY alert( document.body.tagName ); // BODYundefinedundefined

    undefinedundefined

    Is there any difference between undefinedundefinedtagName and undefinedundefinednodeName?undefinedundefined

    undefinedundefined

    Sure, the difference is reflected in their names, but is indeed a bit subtle.

    undefinedundefined
      undefinedundefined
    • The undefinedundefinedtagName property exists only for undefinedundefinedElement nodes.undefinedundefined
    • undefinedundefined
    • The undefinedundefinednodeName is defined for any undefinedundefinedNode: undefinedundefined
        undefinedundefined
      • for elements it means the same as undefinedundefinedtagName.undefinedundefined
      • undefinedundefined
      • for other node types (text, comment, etc.) it has a string with the node type.
      • undefinedundefined
      undefinedundefined
    • undefinedundefined
    undefinedundefined

    In other words, undefinedundefinedtagName is only supported by element nodes (as it originates from undefinedundefinedElement class), while undefinedundefinednodeName can say something about other node types.undefinedundefined

    undefinedundefined

    For instance, let's compare undefinedundefinedtagName and undefinedundefinednodeName for the undefinedundefineddocument and a comment node:undefinedundefined

    run undefinedundefined undefinedundefined undefinedundefined undefinedundefinedundefinedundefined

    undefinedundefined

    If we only deal with elements, then we can use both undefinedundefinedtagName and undefinedundefinednodeName - there's no difference.undefinedundefined

    undefinedundefined

    ``undefinedundefinedsmart header="The tag name is always uppercase except in XML mode" The browser has two modes of processing documents: HTML and XML. Usually the HTML-mode is used for webpages. XML-mode is enabled when the browser receives an XML-document with the header:Content-Type: application/xml+xhtml`.undefinedundefined

    undefinedundefined

    In HTML mode undefinedundefinedtagName/nodeName is always uppercased: it's undefinedundefinedBODY either for undefinedundefined<body> or undefinedundefined<BoDy>.undefinedundefined

    undefinedundefined

    In XML mode the case is kept "as is". Nowadays XML mode is rarely used.

    undefinedundefined

    innerHTML: the contents

    undefinedundefined

    The undefinedundefinedinnerHTML property allows to get the HTML inside the element as a string.undefinedundefined

    undefinedundefined

    We can also modify it. So it's one of the most powerful ways to change the page.

    undefinedundefined

    The example shows the contents of undefinedundefineddocument.body and then replaces it completely:undefinedundefined

    run undefinedundefined undefinedundefined

    A paragraph

    undefinedundefined
    A div